Repository navigation
fix(lint)!: the save door refuses a list view written into a package and bound outside its closure - #24630
Conversation
…s reported, not skipped (#24563) Under a package closure (ruling #9612) the runtime gate now states the closure it narrowed to (AuthoringRuleContext.packageScope), and validateListViewFieldRefs reports an unresolvable binding as list-view-object-outside-closure (warning) instead of skipping the list view. CLI and no-package writes are unchanged. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…4563) Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…s options type (#24563) Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…ut-of-closure advisory (#24563) Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…st-view-out-of-closure
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f6c30be837a91e3c6901233fa0b5b875b250f506 && git checkout f6c30be837a91e3c6901233fa0b5b875b250f506
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cc95f5e63e3c96acafa55378a57d06c086e7d639 700c21cbbd4a1bfd7af1c6ce1872ecb578fa4821 && git checkout -B drift-repro cc95f5e63e3c96acafa55378a57d06c086e7d639 && git merge --no-ff 700c21cbbd4a1bfd7af1c6ce1872ecb578fa4821
node scripts/docs-audit/affected-docs.mjs --json cc95f5e63e3c96acafa55378a57d06c086e7d639
|
席位核对 (seat check) · PR #24630 ·
|
| item | reading |
|---|---|
| Shape | Draft, base main, assignee zhuangjianguo. Line 1 is Fixes #24563. 9 files, +576 / −9. Not governed. |
| What holds | The gate states the closure on the rule context (AuthoringRuleContext.packageScope, one predicate shared with narrowObjectsToPackageClosure, limbs untouched). validateListViewFieldRefs reports an out-of-closure binding once per list view at the key it was read from, with a hint naming the remedy (declare the dependency). The in-closure field refusal is pinned at both layers. Overlay writes and the CLI are unchanged. P3: 11 shipped views and 39 bindings, with 0 outside their closure. |
| Question 2: answered B (refuse) | list-view-object-outside-closure is an error: the publish-mode write is refused 422 INVALID_METADATA with the same location and hint. A nosuch_obj binding inside a package is refused the same way. Why: • The maintainer pulled this card into 18.0 as a refusal. The explanation the maintainer approved read 「18.0 起保存时直接拒绝」, and the claim records "shipped as a refusal". An advisory leaves the card's own reproduction storing a view that fails at render, and an AI loop that reads only the status code still stores it. • The dataset door already refuses the same out-of-closure reference under the same closure ( object-reference-unknown, error, P2), and ruling #9612 says "may reference". • P3 found 0 shipped views affected. • The flow door's advisory is a separate question, and this PR does not change it. |
| Question 1: follows | The diff now narrows the door and also adds public surface (an optional context member, an optional argument, two exports). Clause-②: yes (narrowing) is the spelling the gate reads for widen-plus-narrow (clause2-line.mjs, as on PR #24458). The claim's no (narrowing) is amended in a note on the card in this act. |
| The patch | One commit. (1) The severity is error. Stored rows are judged at their next save, as with #24491. (2) Both pin layers flip: out-of-closure and nosuch_obj are refused 422 at publish-mode saveMetaItem and at publishPackageDrafts. The in-closure, declared-dependency, platform-object, overlay and flow controls stay unchanged. (3) The changeset: @objectstack/lint minor with a ! title, Clause-②: yes (narrowing), FROM / TO / Fix lines (the fix: declare the dependency in the writing package's manifest, or bind an object it may reach), and the ADR-0087 disposition check-adr-0087-registration decides. (4) The docs sentence and the PR body say refused, not advised. |
| Accepted deviations | (2) Files beyond the claim (authoring-rules.ts, the only typed channel to a rule; index.ts; the docs sentence made false). (3) One early tsc ran outside the lock; every later run went through it. (4) P1 was probed through the real saveMetaItem over the stub engine. (5) There is no conflict with main. (6) The first commit's fix(lint)!: subject now matches the delivered change. (7) The trailers follow AGENTS.md. |
| Findings | (a) An overlay write of a list view bound to a nonexistent object answers success with no advisory: no rule judges a view's data.object without a package. The seat files it at adoption if the review agrees that its reach holds. (b) The three doors disagree on an out-of-closure object reference (flow advisory, dataset and now view refused). Noted for the same card, not filed separately. |
| Landing | After the patch, a CONTRACT_REVIEW_TIER review on the new head (narrowing face), then landing on a green head. |
Generated by Claude Code
|
CI red on
Generated by Claude Code |
… the save door (#24563 patch round 1) list-view-object-outside-closure is an error: a publish-mode write of a list view into a package whose bound object is outside the package's closure (or nowhere) is refused 422 INVALID_METADATA at the binding, with the declare-the-dependency hint. Both pin layers flip to the refusal; the in-closure, declared-dependency, platform-object, overlay and flow controls are unchanged. Changeset: minor, !, Clause-② yes (narrowing), FROM/TO/Fix, ADR-0087 not-required (no-migration-prescription). Fixtures that bound a package's view outside its closure are re-trued to an object the package may reach, assertions unchanged: the objectql publish-drafts conformance view (a platform object), the objectql duplicatePackage names-in-step source package (declares the third package it references), and the dogfood cross-package default container (the Studio-created package declares the showcase). Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
Seat answer · PR #24630 · patch round 1 open question 1 ·
|
… copy was refused (#24563 patch round 2) Seat answer A (6277565064): the case 'a name already claimed is refused up front' now pins two refusals, the ticket object and then view/ticket_views with list-view-object-outside-closure at views[0].object; the copy keeps the source's ticket, outside the copy package's closure. The changeset names duplicatePackage among the doors. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…geset/pre.json does on main (#24563) Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Narrowing face, read against the net diff from merge base c395405 (13 files, +663 / -15) and the cited code at that base and at this head. Inputs: card #24563 (body and every comment, the four os-dev reports included), PR #24630 (title, body, file list, diff), ruling #9612, the riders 6271671580 / 6277575090 (#6367) and 6277594189 (#6024), the seat records 6273946447, 6274125180 and 6277565064, and this head's check-runs. Nothing was built, run or re-run; the only commands were git reads, gh single-item GETs, and the record template. ① Derived judgmentsThe narrowing is the ruled one (ruling B, record 6273946447), and no wider. Right.
Every door the changeset's FROM / TO names is real. Right, with one precision note.
The finding. Right.
The four fixture re-trues outside the claim. Right.
The pins. Right, and the ablation readings are consistent with their control flow.
Changeset, semver, Clause-②. Right.
Docs. Right. The Measured before crossing. Right as far as reading reaches. The 11 shipped views are the 11 view files in ② Semver level
③ Boundary flagsDev deviations, each answered:
Open questions: round 0 Q1 and Q2 were answered by the seat (B, then the claim amended); round 1 Q1 was answered A in 6277565064. All three are closed on this head. Out-of-scope findings:
Riders: the engine rider on #6367 (6271671580, amended 6277575090) names Check-runs on this head, read at 2026-10-19T22:13Z: 40 completed success, 9 completed skipped, 5 in progress ( Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Contract review adopted ·
Generated by Claude Code |
Fixes #24563
Clause-②: yes (narrowing)
At the runtime save door, a list view written into a package is now judged against that package's closure (ruling #9612: the package, its declared dependencies, platform/system objects, and provenance-less overlay rows) instead of being skipped:
422 INVALID_METADATAwith onelist-view-object-outside-closureissue aterror, located at the key the binding is read from (views[0].data.object), and nothing is stored. The hint names the remedy: declare a dependency on the package that defines the object in thedependenciesof the writing package's manifest. That is the verdict the dataset door already gives an out-of-closure base object under the same closure (P2). The flow door's advisory is unchanged.duplicatePackagewrites its copies through the same door. A copied list view whose bound object stays outside the copy package's closure is refused with the same rule, for example when that object's own copy was refused. Drafts stay ungated, and a stored row is judged at its next save.columnsfield is refused422 list-view-field-unknown, as the overlay door refuses it (unchanged, now pinned at both layers).Patch rounds 1 and 2: refused, not advised
The seat answered the round-0 question with B (record
6273946447on this PR) and amended the claim's line toClause-②: yes (narrowing)(6273958360on #24563).list-view-object-outside-closureis now anerror, so the door narrows: the binding the round-0 head reported is refused. The public surface still grows by an optionalAuthoringRuleContext.packageScope, an optional second argument tovalidateListViewFieldRefs, and two exports (LIST_VIEW_OBJECT_OUTSIDE_CLOSURE,ListViewFieldRefOptions). Widen plus narrow isClause-②: yes (narrowing). The changeset isminorwith a!title, FROM / TO / Fix lines and the ADR-0087 dispositionnot-required (no-migration-prescription). Round 0's CI red (Test Core (5/6), the objectql publish-drafts conformance fixture) and the consumer sweep's fixtures are re-trued in the same commit; see "Fixtures re-trued". Patch round 2 takes the seat's answer A (6277565064). TheduplicatePackagecase whose copied view now stays outside the copy's closure is re-pinned to the two refusals, and the changeset's door list namesduplicatePackage.What changes
packages/lint/src/validate-list-view-field-refs.ts. Skip 1 (:818) splits in two:ListViewFieldRefOptions.packageScope), a binding missing fromstack.objectsis refused once per list view (error), at the key it was read from (data.object, else the container'sobjectName/object, else the object'sname). The positions are not judged against anything else;An empty graph under a closure is still judged, because every binding is then outside it. The module header gains the section that explains this, and its "does NOT own" note names the one exception.
packages/lint/src/runtime-gate.ts.runRuntimeAuthoringRulesputspackageScopeon the rule context exactly when it narrowed the snapshots. One predicate,packageScopeInForce, now decides both that andnarrowObjectsToPackageClosure's early return. The four limbs are untouched. The guard's behaviour is byte-identical, and the existing "narrows NOTHING when no scope / an empty package id" pins hold it.packages/lint/src/authoring-rules.ts(interface only):AuthoringRuleContext.packageScope?, documented as set only by the runtime gate and read only by this rule.packages/lint/src/reference-integrity-suite.ts.ReferenceIntegrityRunOptions.packageScope?. ThevalidateListViewFieldRefsmember hands the scope to the rule. ItsruntimeTypesare unchanged (['flow', 'view', 'object']).validateObjectReferencesis unchanged (['flow', 'dataset'],:224).packages/lint/src/index.ts. Exports the new rule id and options type (rule-id-barrel-exports.test.tsrequires the id).Pins.
Lint level: a block in
runtime-gate.package-closure.test.ts, throughrunRuntimeAuthoringRules, covering:nosuch_objrefused, and refused under an empty closure;isSystem, and a provenance-less overlay object, each keeping its field verdict;nosuch_objstill silent, an empty package id narrows nothing);Protocol level: the engine-lane rider declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 (
6271671580). It is a block inpackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, through the realsaveMetaItem,publishMetaItemandpublishPackageDrafts, over a registry with three packages and a platform object:422 INVALID_METADATAatviews[0].data.object, nothing stored, for the stranger's object and fornosuch_obj; the draft save of the same body is not gated;columnsfield refused422atviews[0].columns[1].fieldand nothing stored, and the known field saves clean, for own, declared-dependency and platform objects;failed[0],INVALID_METADATA, the closure issue), nothing stored, and the in-closure case refused;loaded: 1, errors: 0); its next save into the package is refused and the row is untouched.Test file only.
packages/metadata-protocol/src/protocol.tsis untouched (PR fix(spec,metadata-protocol)!: the /meta door no longer authors sharing rules; Setup lists declared sharing rules #24599).Changeset:
.changeset/24563-list-view-out-of-closure.md,@objectstack/lintminor,!title,Clause-②: yes (narrowing), FROM / TO / Fix lines (FROM and TO namesaveMetaItempublish, the draft promotion,publishPackageDraftsandduplicatePackage), the stored-row sentence, and the ADR-0087 dispositionnot-required (no-migration-prescription)(check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition").Docs: one sentence in
content/docs/deployment/validating-metadata.mdx(see "File surface beyond the claim").Premises (measured on this branch)
P1. Holds. The probe was the real
saveMetaItem/publishPackageDraftsover theprotocol.package-closure-gate.test.tsstub-engine shape: a registry holdingcom.acme.crm(declarescom.acme.core),com.acme.core, a strangercom.other.analytics, and a platform object. The body is the card's, a flattened list overlay withcolumns: [title, zzz_nope]. It ran before and after through the same probe; the HTTP door was not booted.origin/main5a871e4e41, lint built from it):com.acme.crm, bound to the stranger's object:RESOLVED success=true state=active advisories=[];nosuch_obj:RESOLVED success=true state=active advisories=[];REFUSED 422 INVALID_METADATA issues=[["list-view-field-unknown","error","views[0].columns[1].field"]];publishPackageDrafts:outcome=published published=1 failed=[] probes={"issues":[],...}.422 list-view-field-unknown.a0c24ab647), through the protocol block on the same harness:422 INVALID_METADATA,issues=[["list-view-object-outside-closure","error","views[0].data.object"]], no active row;nosuch_obj: the same refusal;422 list-view-field-unknown, andnosuch_objthere200withadvisories=[]);publishPackageDrafts:success=false publishedCount=0,failed[0]INVALID_METADATAwith the closure issue;view-container-cross-package-default.dogfood.test.ts(real showcase composition, REST) run with its Studio-created package declaring NO dependency answeredexpected 422 to be 200onPUT /api/v1/meta/view/os_qa_keyed_probe?package=com.example.repairassetsandexpected 'refused' to be 'published'onPOST /packages/com.example.repairassets/publish-drafts. With the dependency declared it passes 4/4.8a295d8477, for the record: the same probe answeredRESOLVED success=truewith the finding atwarninginadvisories.)expected [] to deeply equal [ 'list-view-object-outside-closure' ].P2. Holds: advisory.
validateFlowTriggerReadiness, issue codeflow-trigger-unknown-object, severitywarning.packages/lint/src/validate-flow-trigger-readiness.ts:446-449, the record-trigger branch:objectName && !objectNames.has(objectName) && !objectName.startsWith('sys_')withseverity: 'warning'. ThetimeRelativetwin is at:498-501.authoring-rules.ts:1365(surfaces: CLI_AND_RUNTIME,runtimeTypes: ['flow']).runtime-gate.package-closure.test.ts:246("Measured severity here is advisory (warning), so it reports rather than refuses"), and at the door atprotocol.package-closure-gate.test.ts:211(['flow-trigger-unknown-object'],success: true).validateObjectReferencesruns ondatasetwrites, and under the same closure it REFUSES an out-of-closure base object:errors=["error:object-reference-unknown@datasets.crm_ds.object"], and[]for the same body with no package. So the door had two verdicts for "an object outside the package's closure": flow advisory, dataset refusal. Round 0 gave the view the flow's; patch round 1 gives it the dataset's (the seat's answer B). The flow door is unchanged.P3. Holds. No shipped view binds an object outside its package's closure. The door's
viewrule set (validatePresetComparands,validateReferenceIntegrity,validateVisibilityPredicates,validatePredicatePathRefs) was run over every shipped view, with its package's scope and without, before and after this change.*.object.tsunderpackages/outsidespec. All 83 are system objects (isSystemObject), so 118 objects in all.dependencies:examples/app-crm(composed stack):com.example.crm, none, 3 views;examples/app-todo(composed stack):com.example.todo, none, 1 view;examples/app-showcase(the barrels the config imports, because the composed config imports unbuilt connectors):com.example.showcase, none, 6 views;examples/app-multi-package:com.example.multi.core(none) andcom.example.multi.orders(com.example.multi.core), 0 views;create-objectstackblank template (its barrels): 0 views;packages/qa/downstream-contract(ContractStack):com.objectstack.downstream-contract, none, 1 view.objectunder its package's scope: 0 list-view findings.data.object.showcase_task, undercom.example.crm, draws 3 advisories (listand its twolistViews) and 0 errors at round 0.a0c24ab647, universe 119 objects after main addedsys_identity_email): 11 views, 39 bindings, 0 outside the closure, 0 refused, scoped or whole. The control now draws 3 errors (list-view-object-outside-closure×3) and 0 advisories.Mechanism assumptions
G1. The list-view rule reports the binding itself, scoped by a stated closure. Adding
'view'tovalidateObjectReferenceswas measured and rejected, for three reasons:errorfor an unprefixed unresolved name butwarningfor a platform-prefixed one, and its severity is not keyed to the closure. Package scope, out-of-closure:["error:object-reference-unknown@views[0].object"];views[].object. A body binding throughdata.objectalone answered[];["error:object-reference-unknown@views[0].object"]with no package; today200), which changes the overlay door.Reporting only under a stated closure keeps the overlay door and the CLI byte-identical. The only channel that tells a rule a closure is in force is the rule context, hence
packageScope.G2. Held. Both layers are pinned (above). There was no
protocol.tschange.G3. Held at patch round 1.
minor,!title,Clause-②: yes (narrowing), FROM / TO / Fix lines (the fix: declare the dependency on the defining package in the writing package's manifestdependencies, or bind an object it may reach), following PR feat(lint)!: the visibility-predicate family runs on the page save door (ruling B on #23388) #24509's changeset.check-adr-0087-registration --base origin/maindecidesnot-required (no-migration-prescription): exit 0, "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition". (Round 0 had it falsified by P2's advisory.)G4. Main moved
5a871e4e41→c3954057fe(5 commits, none inlintormetadata-protocol;specmoved). It was merged withbash scripts/pm/os-regen-merge.sh: steps 1–3, with no additional changes. Step 4: themetadata-protocoldependency closure was rebuilt, andpnpm --filter @objectstack/spec check:generatedreports all 14 generated artifacts up to date. This branch edits no generated artifact.Verification
Patch rounds 1 to 3 (final head
700c21cbbd)8a295d8477:a0c24ab647(round 1),483b3ec995(round 2: theduplicatePackagere-pin and the changeset door list), and700c21cbbd(round 3: one changeset prose line). Round 3 replaces the parenthetical ".changeset/pre.jsonis open innextpre mode" with ".changeset/pre.jsonis inexitmode, so the next version pass cuts 18.0.0 and this change ships in it", measured onmainsincec46b5f14cf(chore(release): exit Changesets pre mode (next), so the next version pass cuts the 18.0.0 GA #24555). There is no merge, because no file this PR touches changed onmain(nowcc95f5e63e).483b3ec995, each run underos-verify-lock.shafter rebuilding theobjectqldependency closure:pnpm --filter @objectstack/objectql test --maxWorkers=2: 447 files / 8414 tests passed;protocol-duplicate-package-unprefixed-object.test.ts: 4/4;runtime-gate.package-closure.test.ts: 27/27;protocol.runtime-authoring-gate.test.ts: 162/162.a0c24ab647tree. It covered every package whose tests save or publish through the gate (git grep -l "publishPackageDrafts\|saveMetaItem" -- "packages/**/*.test.ts"). Each ranpnpm --filter PKG test --maxWorkers=2underos-verify-lock.sh:@objectstack/lint148 files / 6934 tests passed;@objectstack/metadata-protocol248 passed, 3 skipped / 20733 passed, 19 skipped;@objectstack/objectql445 passed, 2 failed / 8407 passed, 7 failed. 6 wereprotocol-duplicate-package-names-in-step.test.ts(re-trued, 6/6) and 1 was theduplicatePackagecase re-pinned in round 2;@objectstack/rest300 / 6095 passed;@objectstack/runtime423 / 6655 passed;@objectstack/core106 / 2580;@objectstack/metadata63 / 892;@objectstack/platform-objects78 / 1188;@objectstack/plugin-security188 / 3754;@objectstack/service-automation192 / 2567;@objectstack/service-cluster7 / 105;@objectstack/service-datasource45 / 838;@objectstack/cli(both projects) 474 passed, 1 skipped / 6018 passed;@objectstack/dogfood: the 7 files the grep names plus the re-truedview-container-cross-package-default.dogfood.test.ts, 8 files / 51 tests passed. The rest of the dogfood suite was not run locally (main's full battery runs it). Agit grepfor package-scoped view writes (meta/view…?package=) inpackages/qa/dogfood/testnames only the re-trued file;@objectstack/specwas not run: it has no workspace dependency (pg-connection-string,zod), so no spec test can reach@objectstack/lint, and its grep hits are prose.lint(withcheck:test-typecheck),metadata-protocol,objectqlanddogfoodexit 0.warning.severity: 'error'becameseverity: String(object) === 'ablated_24563_warning' ? 'error' : 'warning'throughscripts/ablation-replace.mjs(anchor 1 → 0, blobf38af8f08988→377fdb85d3dd). Lint was rebuilt, and the marker was in 4 built files.git diff HEADempty, rebuild,--absentfrom all 20 built files, tree clean. The lint file passed 27/27 and the protocol file 162/162.f38af8f08988) is unchanged in483b3ec995.Round 0
cab356c0ce(the same code asfe5bafb2be, which added only the changeset and the docs sentence):pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 248 files passed (3 skipped), 20731 tests passed (19 skipped).typecheckpassed.8a295d8477:pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 148 files, 6934 tests passed.typecheck(withcheck:test-typecheck) passed.metadata-protocol:protocol.runtime-authoring-gate.test.ts,protocol.package-closure-gate.test.tsandprotocol-publish-drafts-closure.test.tspassed 175/175.typecheckpassed. The merge brought nometadata-protocolorlintchange.fe5bafb2be):scripts/ablation-replace.mjs:if (scope) reportOutsideClosure(...)becameif (scope && String(object) === 'ablated_24563_no_report') reportOutsideClosure(...). Anchor 1 → 0, blobdf4ce7aa8c22→7340a202512b. Lint was rebuilt, andablation-dist-preflightfound the marker in 4 built files.git diff HEADempty, rebuild, then--absent: absent from all 20 built files, tree clean. The lint file passed 27/27 and the protocol file 160/160.Gates
Verified head
483b3ec995, and the final head700c21cbbd700c21cbbdchanges one prose line in the changeset, and no test or derived family reads it differently. On it,check-adr-0087-registration,check-changeset-no-majorandcheck-empty-changeset(each--base origin/main) exited 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 95 families from 13 changed paths against merge basec3954057f, the same 95 as round 1. Round 2's paths add none. All 95 were re-run verbatim on this head, with each exit code recorded before any pipe.--ran: "95 derived famil(ies) accounted for — 94 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.scripts/check-stack-collection-maps.mjs,scripts/engine-double-contract.pinned.json,scripts/platform-object-tenancy-census.json) changed onmainsince the merge base. None is a file this PR touches, so the branch was not re-merged, per the patch instruction. CI judges the merge ref.check:skill-examples(client-react had no.d.ts) andcheck:lean-entry-closure(objectql/dist/coreabsent) each exited 3. After buildingclient,client-reactandobjectql, both exited 0.check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET). It is NOT MEASURED and left to CI.check-engine-split-ratioshallow refusal is cleared by the deepened clone; it exits 0 here.check-adr-0087-registration --base origin/mainexit 0 ("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition");check-changeset-no-major --base origin/mainexit 0 ("This diff introduces nomajorbump");check-empty-changeset --base origin/mainexit 0.test:repoandpnpm lint(CI-owned).Round 0
These were run on the merge head
8a295d8477.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, derived 93 families from 9 changed paths against merge basec3954057f. Each ran verbatim, with its exit code recorded before any pipe.--ranreconciliation: "93 derived famil(ies) accounted for — 92 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.check-adr-0087-registration --base origin/main("this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)");check-empty-changeset --base origin/main;check-changeset-no-major --base origin/main(locally it reads no PR body; CI reads this one);check:nul-bytes,check:cross-package-test-inputs,check:test-source-alias,check:doc-authoring,check:type-check-coverage,check:type-check-debt,check:engine-double-contract,check:skill-examplesandcheck:lean-entry-closure. The client, client-react and objectql dependency closures were built first.check:dual-build-cjs-loads. It exited 3 (PREREQUISITE NOT MET) because it needs every package built, and 33 export entries here have nodist/(apps,cloud-connectionand the connectors among them). Left to CI.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts are up to date, after the merge and a spec rebuild.check-platform-checklist --changed-from: not run. No export was renamed or removed; two were added.test:repo(no repo-project test changed, and the derivation does not name it) andpnpm lint(a repo-wide scan, which CI owns).origin/mainhas since moved to973917c579. The 6 commits touch nolintfile and no file this PR edits (one is inmetadata-protocol's seed loader). The branch was not re-merged, because there is no conflict.File surface beyond the claim, and why
packages/lint/src/authoring-rules.ts: one optional interface member,AuthoringRuleContext.packageScope. It is the only typed channel from the gate to a rule (G1). No open PR touches the file (17 open PRs' file lists read in this act).packages/lint/src/index.ts: two exports.rule-id-barrel-exports.test.tswent red without the id (1 failed of 6934).content/docs/deployment/validating-metadata.mdx, one sentence. Its "That changes what a rule can resolve, never what it judges" became false for this rule, so it now reads "never which rules run" and says a list view bound outside the closure is refused.Fixtures re-trued (patch round 1), assertions unchanged; one case re-pinned (patch round 2)
Each fixture saved or published a package's view bound outside that package's closure. Round 0 gave each an advisory: the names-in-step and dogfood cases passed, and the conformance file's advisory-free assertions went red (round 0's CI red). Under the refusal each write is refused. Each is re-trued so its view binds an object the package may reach, and no assertion is weakened.
packages/objectql/src/publish-package-drafts-response-conformance.test.ts(round 0's CI red,Test Core (5/6)). The views boundcase, which exists nowhere in that harness. Six cases were refused under B: plain batch, probes, materializer, advisories, and two invariants.byte-stabilitypassed only vacuously (an emptypublished[]).viewBodynow bindssys_metadata, a platform object the harness registers, with its declaredidcolumn. The view element stays advisory-free beside the flow that carries the advisory. 16/16.packages/objectql/src/protocol-duplicate-package-names-in-step.test.ts. The source package saved views of a third package's objects (dupext_account.board,dupsrc_ext.board) without declaring it. The source now declaresdependencies: { [R]: '*' }. The objects stay outside the package, which is what "a view item of an object outside the package keeps its name" pins, and the copy inherits the declaration with the manifest. 6/6.packages/qa/dogfood/test/view-container-cross-package-default.dogfood.test.ts. The Studio-createdcom.example.repairassetssaved containers forshowcase_taskwithout declaring the showcase. ItsPOST /packagesmanifest now declaresdependencies: { [SHOWCASE]: '*' }. The container is still for an object another package ships, which is the test's subject. 4/4, and without the declaration it reads422/refused(P1 above).These are files outside the claim's surface: the engine lane (
packages/objectql) andpackages/qa/dogfood.Re-pinned (patch round 2, seat answer A
6277565064):packages/objectql/src/protocol-duplicate-package-unprefixed-object.test.ts, case "a name already claimed is refused up front, at the object, naming the rename". The source's viewticket_viewsis inside its own closure. Because theticketcopy is refused, the copy of that view keeps the source'sticket, which is outside the copy package's closure, so the copy is refused with the same rule.res.failedis pinned to exactly two entries, in order: theticketobject refusal, with every existing assertion on it; then{ type: 'view', name: 'ticket_views' }, whoseerrorcarriesviews[0].object [list-view-object-outside-closure](the rule id and path, not the message).The riders are declared by the seat: the three objectql test files on #6367 (
6277575090), and the dogfood test on #6024 (6277594189).Acceptance notes
nosuch_objanswers200withadvisories: [], pinned as unchanged here (out of scope by the seat's record). A flow on the same overlay write getsflow-trigger-unknown-object. The seat files it at adoption if its reach holds.list-view-object-outside-closurehas noos explainentry. The CLI never emits it, and the hint carries the remedy.listViewsentry retargetsdata.objectoutside the closure is refused too (pinned in the lint block's flow case). No shipped object retargets (P3).dependencieswas not measured.POST /packagesaccepts them (the dogfood fixture above).Generated by Claude Code