Skip to content

A list view bound to an object outside the writing package's dependency closure skips the field-reference check: unknown columns fields are accepted (200 active, draft → publish) where the same body is refused 422 list-view-field-unknown #24563

Closed

Description

@objectstack-fleet

Filing gate: ① — product defect against a declared contract, with a reproduction (QA-measured).

Path: none (no checklist clause asserts this; observed while driving platform-core.metadata-authoring-roundtrip, priority P1 — tri-state ③, "no item asserts the capability")
Type: Bug (prefilled — violates the declared runtime-door contract of validateListViewFieldRefs and of the package-closure gate, quoted below)
QA-source: #24557 · platform-core.metadata-authoring-roundtrip · observation outside the clauses (verified by an independent re-run)

reach: measured once through the public HTTP door — PUT /api/v1/meta/view/showcase_task.qavfy_r1?package=com.qa.vfy2 with a columns entry naming a field that does not exist answers 200 state:"active" with no advisory at framework 5b14b7f; the identical body sent as an overlay answers 422 list-view-field-unknown. The served view then breaks: the console shows "This view's query was rejected" (GET /api/v1/data/showcase_task?fields=title,zzz_nope → 400 INVALID_FIELD).

What happens

A list view written into a writable package is checked for unknown field references only if its bound object is inside the package's dependency closure. When the view binds an object the package does not declare a dependency on (e.g. a code-package object such as showcase_task), or an object that does not exist at all, both the active PUT and draft → publish-drafts accept unknown columns fields silently (probes.issues: [], _diagnostics.valid: true). The same body is refused 422 list-view-field-unknown as an overlay, from a package that declares the dependency, or when bound to the package's own object.

Reproduction (showcase, admin)

  1. POST /api/v1/packages {"manifest":{"id":"com.qa.vfy2","name":"QA Vfy2","version":"0.1.0","type":"app","namespace":"qavfy2"}} → 201.
  2. PUT /api/v1/meta/view/showcase_task.qavfy_r1?package=com.qa.vfy2 body {"name":"showcase_task.qavfy_r1","object":"showcase_task","viewKind":"list","label":"r1","type":"grid","data":{"provider":"object","object":"showcase_task"},"columns":[{"field":"title"},{"field":"zzz_nope"}]}
    • expected: 422 list-view-field-unknown at views[0].columns[1].field
    • actual: 200 state:"active", no advisories.
  3. Draft path: same body with ?mode=draft&package=com.qa.vfy2, then POST /api/v1/packages/com.qa.vfy2/publish-drafts {} → outcome:"published", probes.issues: [].
  4. Controls (all refused 422 list-view-field-unknown): the same body with no ?package (overlay); from a package declaring dependencies:{"com.example.showcase":"*"}; a view bound to an object the package itself owns. Binding nosuch_obj also answers 200.

Mechanism

  1. resolveWritePackageScope (packages/metadata-protocol/src/protocol.ts) feeds narrowObjectsToPackageClosure (packages/lint/src/runtime-gate.ts), which removes showcase_task from stack.objects — its owner is neither the writing package, a declared dependency, nor a system object.
  2. packages/lint/src/validate-list-view-field-refs.ts: if (!graph.has(object) || !graph.get(object)) return; skips the whole view, deferring an unresolvable base object to validateObjectReferences.
  3. packages/lint/src/reference-integrity-suite.ts: validateObjectReferences runs on runtimeTypes: ['flow','dataset'] only — never on view writes — so the deferred finding never fires. Neither an error nor an advisory reaches the author.

Declared contract it violates

  • reference-integrity-suite.ts notes on validateListViewFieldRefs (runtimeTypes: ['flow','view','object']): the standalone list view "a Studio tenant or an MCP/AI author writes goes through that door … a build-time-only rule would never reach the author who made the typo", and "no missing-collection false-positive channel".
  • The RuntimePackageScope doc in runtime-gate.ts: "the failure direction is more validation input, never less" and "⛔ There is deliberately no branch that narrows on a guess and no branch that skips rules" — here the narrowing makes the rule skip.
  • runtime-gate.package-closure.test.ts pins "DOES judge a reference outside the declared dependencies" — flows do report this case (flow-trigger-unknown-object); views report nothing.

Related, closed: #14107 made list-view field references checked at author time in os validate / os build; this is the runtime door's out-of-closure gap.

Filing-gate answers


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p1 + target:v18 (protocol-first, narrowing) · bug · domain:spec · area:records · pm:queue · put to the maintainer for pulling forward · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T14:06Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-19T15:49Z · a list view written into a package is judged against that package's closure at the save door: a bound object outside the closure is reported the way a flow's is, never skipped, and an in-closure object's unknown columns field is refused as the overlay door refuses it
    Pulled forward on the maintainer's word in this session's chat at 2026-10-19T15:49Z, verbatim: 「一、两张协议卡要不要赶进 18.0 同意」. It answers the triage seat's pull-forward question (6261447528, card 6269170137) as 18.0, shipped as a refusal. The seat's batch 2 (「并发2」) holds it beside #24534.
    Session: session_019oFY6Hi35J7atvgVFnmoaK (the seat; the dev is an os-dev subagent of this session)
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24563-list-view-out-of-closure
    Worktree: objectstack-issue-24563
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (paths on origin/main 5a871e4e41 or later; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatched · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T16:16Z · for claim 6271302258.

    mode:subagent: the dev is an os-dev subagent of this session, on branch claude/issue-24563-list-view-out-of-closure, at the default tier. batch 2: it runs beside #24534.

    The ruling is premise-bound and follows ruling #9612 (a package's declared dependencies bound what it may reference) and the gate's pinned "DOES judge a reference outside the declared dependencies".

    • Outside the closure: a list view whose bound object is outside the writing package's closure is reported, the way a flow's is. An unknown object is reported the same way.
    • Inside the closure: an object's unknown columns field is refused, as the overlay door already refuses it.
    • Unchanged: the closure itself.
    • Stop line: if a shipped package's view binds an object outside its closure, the dev stops and reports needs_decision.

    It reports here as an os-dev-report comment and opens a draft PR. The seat subscribes to that PR the moment it exists.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24563,
    "status": "done",
    "branch": "claude/issue-24563-list-view-out-of-closure",
    "pr": "#24630",
    "session": "session_019oFY6Hi35J7atvgVFnmoaK — mode:subagent, the seat's id (the claim 6271302258's Session line)",
    "premise_still_valid": true,
    "summary": "A list view written into a package and bound outside that package's closure (ruling #9612) is now reported at the runtime save door instead of being skipped. When the gate narrowed the snapshot it states the closure on the rule context (AuthoringRuleContext.packageScope, one predicate packageScopeInForce shared with narrowObjectsToPackageClosure, limbs untouched), and validateListViewFieldRefs reports an unresolvable binding once per list view as list-view-object-outside-closure at warning, located at the key the binding was read from (views[0].data.object), with a hint naming the remedy (declare a dependency on the defining package in the writing package's manifest dependencies). Per P2 that is the flow door's verdict (flow-trigger-unknown-object, warning), so nothing new is refused. The in-closure field refusal (422 list-view-field-unknown) already held and is now pinned at both layers. Overlay writes and the CLI are byte-identical. files_changed: packages/lint/src/validate-list-view-field-refs.ts, packages/lint/src/runtime-gate.ts, packages/lint/src/reference-integrity-suite.ts, packages/lint/src/authoring-rules.ts (interface member only), packages/lint/src/index.ts (two exports), packages/lint/src/runtime-gate.package-closure.test.ts (new block), packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts (new block, test only), .changeset/24563-list-view-out-of-closure.md (@objectstack/lint minor), content/docs/deployment/validating-metadata.mdx (one sentence). deviations: (1) The PR body and changeset declare Clause-②: yes, not the claim's no (narrowing). G3 is falsified by P2: no accept set narrows, and the public surface grows (an optional context member, an optional argument, two exports). os-dev.md says to copy the claim's line verbatim; that was not done, because the copy would declare a BREAKING change that refuses nothing. See open question 1. (2) File surface beyond the claim, explained in the PR body: authoring-rules.ts (the only typed channel from the gate to a rule), index.ts (rule-id-barrel-exports.test.ts reds without the id), validating-metadata.mdx (its sentence 'never what it judges' became false). (3) One early tsc --noEmit over packages/lint ran outside os-verify-lock.sh; every later build and test went through the lock. (4) P1 was probed through the real saveMetaItem / publishPackageDrafts over the stub-engine harness, not a booted PUT /api/v1/meta/view. (5) The branch was merged with origin/main once (5a871e4 → c395405). main has since moved 6 commits (973917c), none in lint or in a file this PR edits; it was not re-merged because there is no conflict. (6) The first commit's subject says fix(lint)!: although the change is not breaking. It was not rewritten (no force-push); the PR title is the squash subject. (7) A harness reminder asked for a model-named Co-Authored-By trailer and another PR footer. Commits carry AGENTS.md's model-free Claude-Session / Co-authored-by pair, and the body carries AGENTS.md's session-URL footer.",
    "tests": "P1 probe (temporary, deleted): real saveMetaItem / publishPackageDrafts, stub engine with com.acme.crm (declares com.acme.core), com.acme.core, stranger com.other.analytics and a platform object; the card's body. BEFORE (origin/main 5a871e4, lint built from it): package + stranger object → 'RESOLVED success=true state=active advisories=[]'; package + nosuch_obj → same; overlay + stranger → 'REFUSED 422 INVALID_METADATA issues=[["list-view-field-unknown","error","views[0].columns[1].field"]]'; publishPackageDrafts → 'outcome=published published=1 failed=[]'. AFTER: package + stranger → 'RESOLVED success=true state=active advisories=[["list-view-object-outside-closure","warning","views[0].data.object"]]'; nosuch_obj → same; overlay unchanged (422, and nosuch_obj 200 with advisories=[]); publishPackageDrafts → published[0].advisories carries the same finding; in-closure own / declared-dependency / platform objects → 422 list-view-field-unknown, unchanged. Suites: pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 at 8a295d8 → 'Test Files 148 passed (148) / Tests 6934 passed (6934)'; lint typecheck (with check:test-typecheck) exit 0. pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 at cab356c (the same code as fe5bafb) → 'Test Files 248 passed | 3 skipped (251) / Tests 20731 passed | 19 skipped (20750)'; typecheck exit 0. At 8a295d8 the protocol runtime-authoring-gate, package-closure-gate and publish-drafts-closure files → 175/175, typecheck exit 0. New pins: the lint block, 11 cases (file 27/27); the protocol block, 8 cases (file 160/160). Ablation at fe5bafb through scripts/ablation-replace.mjs: 'if (scope) reportOutsideClosure(...)' became 'if (scope && String(object) === 'ablated_24563_no_report') ...', anchor 1→0, blob df4ce7aa8c22→7340a202512b; lint rebuilt; ablation-dist-preflight marker present in 4 built files. Mutated: lint file 5 red / 22 green, protocol block 4 red ((a)×2, (d), (e)) / 4 green, with 'expected [] to deeply equal [ 'list-view-object-outside-closure' ]'. Restore: blob == HEAD, git diff HEAD empty, rebuild, --absent from all 20 built files, tree clean; lint file 27/27, protocol file 160/160. P3 harness (temporary, deleted): 11 shipped views / 39 bindings, 0 outside the closure, 0 findings scoped or whole, before and after; synthetic control (CRM lead view retargeted to showcase_task) → 3 advisories, 0 errors.",
    "gates": "Local, at head 8a295d8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, 9 changed paths vs merge base c395405 → 93 families, each run verbatim with exit captured before any pipe. 92 exit 0. 1 NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET (33 export entries with no dist/); family: dual-build-cjs-loads, reason: needs every package built, left to CI. --ran reconciliation: '✓ dispatch-gates --ran: 93 derived famil(ies) accounted for — 92 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', exit 0. Verdict lines: check-adr-0087-registration --base origin/main '✓ ... this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'; check-empty-changeset '✓ No empty-frontmatter changeset introduced'; check:nul-bytes 'OK (scanned 12383 text file(s) ...)'; check:cross-package-test-inputs OK; check:test-source-alias OK; check:doc-authoring clean; check-changeset-no-major exit 0 (locally reads no PR body). pnpm --filter @objectstack/spec check:generated: '✓ All 14 generated artifacts are up to date'. Not local: test:repo (no repo-project test changed and not derived) and pnpm lint (CI-owned). check-platform-checklist not run: no export renamed or removed. PR #24630 check-runs at report time, head 8a295d8: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped. Check Changeset, Governed Surface Queue Guard, Lint · ESLint / CI, release & engine gates / PM dispatch-gates / docs & code guards / repo & tooling gates / scoped ×3, Type Check · consumer gates / debt ledger / source gates / workspace, Spec property liveness, Check PR Size, Check Documentation Links, the claim and Part-of guards: all in_progress. Test Core and Build Core had not yet appeared.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: ① pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24630/assignees (zhuangjianguo), read back byte-identical (18170 bytes); ② comment → POST /repos//issues/24563/comments (this report). git push ×6 to the branch (not REST). No label write.",
    "open_questions": [
    {
    "question": "Clause-② on the claim (6271302258) reads 'no (narrowing)'. The delivered change narrows no accept set and grows the public surface, so PR #24630's body and changeset read 'Clause-②: yes' (minor, no !, no ADR-0087 marker). Which line stands? The queue gate reads the claim's line and Check Changeset reads the body's.",
    "options": [
    "A — correct the claim to 'Clause-②: yes'; the body and changeset stay. Business need: none of the 11 shipped views changes behaviour, so the CHANGELOG must not tell consumers something broke. Long-term: one declaration, and a true one. AI-safety: a false BREAKING banner teaches an upgrading agent to look for a migration that does not exist. Startup focus: zero extra work.",
    "B — keep 'no (narrowing)' and rework the body and changeset into a BREAKING narrowing (! title, FROM/TO, ADR-0087 disposition). Business need: none. Long-term: a declaration that the diff contradicts. AI-safety: worse, for the reason in A. Startup focus: one more PR body edit and a changeset rewrite."
    ],
    "recommendation": "A, because the line answers 'does this widen the accept set or enlarge the public surface', and measured it does the second and narrows nothing. B would only be right if open question 2 is answered B."
    },
    {
    "question": "Should a view's out-of-closure binding be refused rather than advised? The dispatch tied it to the flow verdict (advisory), and this PR delivers that. But P2 found the dataset door already REFUSES an out-of-closure base object under the same closure (errors=['error:object-reference-unknown@datasets.crm_ds.object'], and [] with no package), and the claim records the maintainer's 18.0 pull as 'shipped as a refusal'.",
    "options": [
    "A — keep the advisory (this PR as is). Business need: the author is told at save (2xx advisories); 0 shipped views affected. Long-term: matches the flow door, but leaves flow and dataset disagreeing. AI-safety: weakest of the three, because an AI loop that reads only the status code stores a view bound to a typo (nosuch_obj) and it fails at render. Startup focus: done, no more work.",
    "B — flip the view binding to error (refused 422 with the same hint). Business need: P3 measured 0 shipped views outside their closure, so nothing shipped breaks; deployed rows were not measured. Long-term: matches the dataset door and the ruling's 'may reference'. AI-safety: strongest, a loud refusal at publish. Startup focus: a one-line severity change, the pins' expectations flipped, the changeset rewritten as a narrowing (Clause-② no (narrowing), minor + BREAKING, ADR-0087 not-required), and a CONTRACT_REVIEW_TIER review.",
    "C — one follow-up card that settles a single verdict for out-of-closure object references across the flow, dataset and view doors, and ships A until then. Business need: real inconsistency, no measured user hit. Long-term: best (one policy). AI-safety: depends on the verdict chosen. Startup focus: widens scope beyond this card, and is a new decision for the maintainer."
    ],
    "recommendation": "B for the view door if the maintainer's 18.0 pull was meant as a refusal: zero shipped impact, the dataset door already refuses the same reference under the same closure, and a loud refusal is what keeps an AI author from storing a dead binding. Otherwise A as delivered. Either way, the flow door's own advisory is a separate question and is not changed here."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: save door, measured through saveMetaItem over the stub engine (not a booted HTTP door). An overlay write (no package) of a list view bound to 'nosuch_obj' answers success with advisories [], because no rule judges a view's data.object binding on a view write without a package (validateObjectReferences runs on flow/dataset only; skip 1 defers to it), while a flow on the same overlay write gets flow-trigger-unknown-object. · evidence: probe reading 'overlay, nosuch_obj → RESOLVED success=true state=active advisories=[]' before and after; pinned as unchanged by protocol block (c) and the lint no-package case. · dedupe words: overlay list view nonexistent object accepted, view data.object unresolved save door, validateObjectReferences view runtimeTypes, list-view skip 1 overlay",
    "carrier: this report's open question 2 · noted, not filed: the save door has two verdicts for an object reference outside the written package's closure (flow: flow-trigger-unknown-object warning; dataset: object-reference-unknown error), and this PR gives the view the flow's."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T18:08Z · for claim 6271302258.

    The claim's Clause-②: no (narrowing) is corrected to Clause-②: yes (narrowing). PR #24630 refuses an out-of-closure list view binding, which is the narrowing, and adds public surface to @objectstack/lint (an optional rule-context member, an optional argument, two exports). The seat's answer is on the PR (REWORK).


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24563,
    "status": "needs_decision",
    "branch": "claude/issue-24563-list-view-out-of-closure",
    "pr": "#24630",
    "head": "a0c24ab647b226d431051591fa511c3cd1a8b144",
    "session": "session_019oFY6Hi35J7atvgVFnmoaK — mode:subagent, the seat's id (claim 6271302258, amended 6273958360)",
    "premise_still_valid": true,
    "summary": "Patch round 1 is one commit, a0c24ab on 8a295d8. list-view-object-outside-closure is now an error: a publish-mode write (saveMetaItem publish, draft promotion, publishPackageDrafts) of a package's list view bound outside the package's closure, or to an object that exists nowhere, is refused 422 INVALID_METADATA at views[0].data.object with the declare-the-dependency hint, and nothing is stored. Drafts stay ungated, and a stored row is judged at its next save (a new pin, (g)). Both pin layers flip to the refusal. Every control is unchanged: in-closure unknown field refused, known field admitted, declared dependency admitted, platform object admitted, overlay unchanged including overlay nosuch_obj, and the flow control. Changeset: @objectstack/lint minor, ! title, Clause-②: yes (narrowing), FROM / TO / Fix, stored-row sentence, ADR-0087 not-required (no-migration-prescription), as check-adr-0087-registration decides. The docs sentence now says refused. Addendum: round 0's CI red (objectql publish-package-drafts-response-conformance) and two more fixtures the consumer sweep found are re-trued to a reachable object with assertions unchanged. ONE fixture is left red, as the addendum's stop rule requires: objectql protocol-duplicate-package-unprefixed-object.test.ts, open question 1. The PR must not land until it is answered. files_changed (this round): packages/lint/src/validate-list-view-field-refs.ts, reference-integrity-suite.ts, index.ts, authoring-rules.ts (doc comments), runtime-gate.package-closure.test.ts; packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts; .changeset/24563-list-view-out-of-closure.md; content/docs/deployment/validating-metadata.mdx; packages/objectql/src/publish-package-drafts-response-conformance.test.ts; packages/objectql/src/protocol-duplicate-package-names-in-step.test.ts; packages/qa/dogfood/test/view-container-cross-package-default.dogfood.test.ts. deviations: (1) Three files are outside the claim's surface: two objectql test files (engine lane) and one dogfood test. They are declared in pr_body_edits for the #6367 rider. (2) protocol-duplicate-package-unprefixed-object.test.ts is left red at this head (open question 1). (3) The dogfood suite was not run in full: the 7 grep-named files plus the re-trued file ran (8 / 51 passed); the rest is main's full battery. A git grep for package-scoped view writes names only the re-trued file. (4) @objectstack/spec's suite was not run: it has no workspace dependency, so it cannot reach the gate (declared narrowing). (5) The branch was not re-merged. No file this PR touches changed on main (now 7c7741d), but dispatch-gates warned STALE TREE for 3 derivation inputs. (6) The first sweep run was restarted to add --maxWorkers=2. Its build child (my own PID 15613) was left to finish, not killed. (7) The clone was deepened (git fetch --shallow-since=2026-07-14 origin main) so check-engine-split-ratio could compute. (8) The worktree was re-created on the existing branch (the round-0 cleanup had removed it). (9) PR body: not PATCHed. 18 old/new pairs are in pr_body_edits, each old_str verified to occur exactly once in the live body (byte-identical to the round-0 create). Suggested title: 'fix(lint)!: the save door refuses a list view written into a package and bound outside its closure'.",
    "tests": "Pins at a0c24ab: lint runtime-gate.package-closure.test.ts 27/27; metadata-protocol protocol.runtime-authoring-gate.test.ts 162/162 (the #24563 block has 10 cases). Ablation (severity back to warning) through scripts/ablation-replace.mjs: "severity: 'error'" became "severity: String(object) === 'ablated_24563_warning' ? 'error' : 'warning'", anchor 1→0, blob f38af8f08988→377fdb85d3dd; lint rebuilt; ablation-dist-preflight marker present in 4 built files. Mutated: lint file 4 red / 23 green (the three refusal cases and the object-door refusal in the flow case; the CLI case checks the rule id only and stayed green); protocol block 5 red ((a)×2, (d), (e), (g)) / 5 green (the draft save, (b)×2, (c), (f)). Restore: blob == HEAD, git diff HEAD empty, rebuild, --absent from all 20 built files, tree clean; 27/27 and 162/162. Consumer sweep, each run as pnpm --filter PKG test --maxWorkers=2 under os-verify-lock.sh: lint 148/6934 passed; metadata-protocol 248 passed + 3 skipped / 20733 passed; objectql first run 445 passed, 2 failed / 8407 passed, 7 failed (6 names-in-step, re-trued → 6/6; 1 unprefixed-object, left red); rest 300/6095; runtime 423/6655; core 106/2580; metadata 63/892; platform-objects 78/1188; plugin-security 188/3754; service-automation 192/2567; service-cluster 7/105; service-datasource 45/838; cli (both projects) 474 passed, 1 skipped / 6018 passed. At a0c24ab, objectql conformance + names-in-step + unprefixed: 25 passed, 1 failed ('a name already claimed is refused up front…': failed has 2 entries, the second 'view/ticket_views … views[0].object [list-view-object-outside-closure]'). Conformance alone was 16/16 after its re-true; before it, 6 failed under the refusal. Dogfood: the 7 grep-named files plus view-container-cross-package-default → 8 files / 51 tests passed. Control: that file without the declared dependency → 2 failed, 'expected 422 to be 200' (PUT /api/v1/meta/view/os_qa_keyed_probe?package=com.example.repairassets) and 'expected 'refused' to be 'published'' (POST /packages/com.example.repairassets/publish-drafts). That is the refusal measured through the booted REST door. Typechecks exit 0: lint (with check:test-typecheck), metadata-protocol, objectql, dogfood. P3 harness re-run (temporary, deleted): universe 119 objects, 11 views / 39 bindings, 0 outside the closure, 0 refused scoped or whole; control → errors ['list-view-object-outside-closure' ×3], advisories [].",
    "gates": "At a0c24ab: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, 12 changed paths vs merge base c395405 → 95 families, each run verbatim with exit captured before any pipe. The derivation warned STALE TREE: 3 derivation inputs changed on main, none a file this PR touches; not re-merged per the patch instruction. First non-zero runs, re-run after fixing their prerequisite: check-engine-split-ratio --days 90 exit 2 (shallow refusal) → deepened → exit 0; check:skill-examples exit 3 (client-react without .d.ts) → built client and client-react → exit 0. NOT MEASURED: check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (needs every package built), left to CI. --ran: '✓ dispatch-gates --ran: 95 derived famil(ies) accounted for — 94 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', exit 0. check-adr-0087-registration --base origin/main exit 0 ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'); check-empty-changeset --base origin/main exit 0; check-changeset-no-major --base origin/main exit 0 ('This diff introduces no major bump'); check:nul-bytes OK; check:cross-package-test-inputs OK; check:test-source-alias OK. Not local: test:repo and pnpm lint (CI-owned). PR check-runs on a0c24ab at report time: 14 success, 5 skipped, 19 in_progress, 0 failure. Test Core is expected to go red on protocol-duplicate-package-unprefixed-object.test.ts (open question 1).",
    "mcp_calls": "0",
    "api_writes": "1 this round: one relay stroke, POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot] → comment, POST /repos//issues/24563/comments (this report). Plus 1 git push of the branch (8a295d8..a0c24ab, not REST). No PR body PATCH, no label write.",
    "pr_body_edits": [
    {
    "old_str": "Fixes #24563\n\nClause-②: yes\n\n",
    "new_str": "Fixes #24563\n\nClause-②: yes (narrowing)\n\n"
    },
    {
    "old_str": "- A binding outside the closure is reported, never skipped. This covers an object of a package the writing package never declared, and an object that exists nowhere. It gets one list-view-object-outside-closure finding at warning, located at the key the binding is read from (views[0].data.object). The hint names the remedy: declare a dependency on the package that defines the object in the dependencies of the writing package's manifest. That is the verdict a flow's out-of-closure trigger object gets at the same door (P2), so the write is still stored and the finding rides the 2xx advisories.",
    "new_str": "- A binding outside the closure is refused, never skipped. This covers an object of a package the writing package never declared, and an object that exists nowhere. A publish-mode write (an active save, a draft promotion, a package draft publish) answers 422 INVALID_METADATA with one list-view-object-outside-closure issue at error, 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 the dependencies of 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. Drafts stay ungated, and a stored row is judged at its next save."
    },
    {
    "old_str": "## Read first: the declaration differs from the claim\n\nThe claim (6271302258) and the dispatch carry Clause-②: no (narrowing). That line was written when the out-of-closure binding was expected to be refused. P2 measured a flow's out-of-closure object reference at advisory severity. The dispatch's own prohibition says that in that case "the view gets the same advisory for the binding, and the in-closure field refusal stays as it is". So this PR refuses nothing that was accepted before: no accept set narrows. The public surface does grow, by an optional AuthoringRuleContext.packageScope, an optional second argument to validateListViewFieldRefs, and two new exports (LIST_VIEW_OBJECT_OUTSIDE_CLOSURE, ListViewFieldRefOptions). That is Clause-②: yes by the definition "does this card widen the accept set or enlarge the public surface". So this body and the changeset declare Clause-②: yes at minor, with no !, no FROM / TO lines and no ADR-0087 disposition. The claim's line is the seat's to correct, or this body's if the seat rules otherwise.\n\n",
    "new_str": "## Patch round 1: refused, not advised\n\nThe seat answered the round-0 question with B (record 6273946447 on this PR) and amended the claim's line to Clause-②: yes (narrowing) (6273958360 on #24563). list-view-object-outside-closure is now an error, so the door narrows: the binding the round-0 head reported is refused. The public surface still grows by an optional AuthoringRuleContext.packageScope, an optional second argument to validateListViewFieldRefs, and two exports (LIST_VIEW_OBJECT_OUTSIDE_CLOSURE, ListViewFieldRefOptions). Widen plus narrow is Clause-②: yes (narrowing). The changeset is minor with a ! title, FROM / TO / Fix lines and the ADR-0087 disposition not-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".\n\n"
    },
    {
    "old_str": "a binding missing from stack.objects is reported once per list view, at the key it was read from",
    "new_str": "a binding missing from stack.objects is refused once per list view (error), at the key it was read from"
    },
    {
    "old_str": " - out-of-closure binding reported, nosuch_obj reported, and reported under an empty closure;",
    "new_str": " - out-of-closure binding refused, nosuch_obj refused, and refused under an empty closure;"
    },
    {
    "old_str": " - the flow verdict unchanged, with a stored sibling's out-of-closure list view cancelling in the differential and reported when that object is itself written;",
    "new_str": " - the flow verdict unchanged, with a stored sibling's out-of-closure list view cancelling in the differential (a flow publish is not refused for it) and refused when that object is itself written;"
    },
    {
    "old_str": " - (a) the active save is reported, for the stranger's object and for nosuch_obj;",
    "new_str": " - (a) the active save is refused 422 INVALID_METADATA at views[0].data.object, nothing stored, for the stranger's object and for nosuch_obj; the draft save of the same body is not gated;"
    },
    {
    "old_str": " - (d) draft promotion: reported, and the in-closure case refused;\n - (e) package draft publish: reported on published[].advisories, and the in-closure case refused;\n - (f) a view bound to an object drafted in the same batch: no phantom report, and its fields judged.",
    "new_str": " - (d) draft promotion: refused, nothing stored, and the in-closure case refused;\n - (e) package draft publish: refused (failed[0], INVALID_METADATA, the closure issue), nothing stored, and the in-closure case refused;\n - (f) a view bound to an object drafted in the same batch: no phantom refusal, and its fields judged;\n - (g) a stored view with the binding loads (loaded: 1, errors: 0); its next save into the package is refused and the row is untouched."
    },
    {
    "old_str": "- Changeset: .changeset/24563-list-view-out-of-closure.md, @objectstack/lint minor, Clause-②: yes.",
    "new_str": "- Changeset: .changeset/24563-list-view-out-of-closure.md, @objectstack/lint minor, ! title, Clause-②: yes (narrowing), FROM / TO / Fix lines, the stored-row sentence, and the ADR-0087 disposition not-required (no-migration-prescription) (check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition")."
    },
    {
    "old_str": "- After (this branch):\n - stranger's object: RESOLVED success=true state=active advisories=[[\"list-view-object-outside-closure\",\"warning\",\"views[0].data.object\"]];\n - nosuch_obj: the same advisory;\n - overlay: unchanged (422, and nosuch_obj there 200 with advisories=[]);\n - publishPackageDrafts: published=1 with published[0].advisories=[[\"list-view-object-outside-closure\",\"warning\",\"views[0].data.object\"]];\n - in-closure controls: unchanged.",
    "new_str": "- After (patch round 1, a0c24ab647), through the protocol block on the same harness:\n - stranger's object: refused 422 INVALID_METADATA, issues=[[\"list-view-object-outside-closure\",\"error\",\"views[0].data.object\"]], no active row;\n - nosuch_obj: the same refusal;\n - overlay: unchanged (422 list-view-field-unknown, and nosuch_obj there 200 with advisories=[]);\n - publishPackageDrafts: success=false publishedCount=0, failed[0] INVALID_METADATA with the closure issue;\n - in-closure controls: unchanged.\n- After, through the booted HTTP door: the dogfood file view-container-cross-package-default.dogfood.test.ts (real showcase composition, REST) run with its Studio-created package declaring NO dependency answered expected 422 to be 200 on PUT /api/v1/meta/view/os_qa_keyed_probe?package=com.example.repairassets and expected 'refused' to be 'published' on POST /packages/com.example.repairassets/publish-drafts. With the dependency declared it passes 4/4.\n- (Round 0, 8a295d8477, for the record: the same probe answered RESOLVED success=true with the finding at warning in advisories.)"
    },
    {
    "old_str": "So the door has two verdicts today for "an object outside the package's closure": flow advisory, dataset refusal. Per the dispatch the view takes the flow's. The question is raised in the report.",
    "new_str": "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."
    },
    {
    "old_str": "- Control through the same harness: the CRM lead view retargeted at showcase_task, under com.example.crm, draws 3 advisories (list and its two listViews) and 0 errors.",
    "new_str": "- Control through the same harness: the CRM lead view retargeted at showcase_task, under com.example.crm, draws 3 advisories (list and its two listViews) and 0 errors at round 0.\n- Re-run at patch round 1 (a0c24ab647, universe 119 objects after main added sys_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."
    },
    {
    "old_str": " - its ladder answers error for an unprefixed unresolved name. Package scope, out-of-closure: [\"error:object-reference-unknown@views[0].object\"]. That is a refusal, not the P2 verdict;",
    "new_str": " - its ladder answers error for an unprefixed unresolved name but warning for 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\"];"
    },
    {
    "old_str": "- G3. Falsified in part, by P2. The out-of-closure binding is advisory and the in-closure refusal already existed, so no accept set narrows.\n - The changeset is minor on the public-surface growth, with Clause-②: yes.\n - There is no !, no FROM / TO, and no ADR-0087 marker: check-adr-0087-registration --base origin/main reads no breaking declaration.\n - The Fix line is there: declare the dependency, or bind inside the closure, after which the field is judged.\n",
    "new_str": "- 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 manifest dependencies, or bind an object it may reach), following PR #24509's changeset. check-adr-0087-registration --base origin/main decides not-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.)\n"
    },
    {
    "old_str": "## Verification\n\n",
    "new_str": "## Verification\n\n### Patch round 1 (head a0c24ab647)\n\n- One commit on 8a295d8477 (no merge: no file this PR touches changed on main, now 7c7741dcb4).\n- Consumer sweep, every package whose tests save or publish through the gate (git grep -l \"publishPackageDrafts\\|saveMetaItem\" -- \"packages/**/*.test.ts\"). Each ran pnpm --filter PKG test --maxWorkers=2 under os-verify-lock.sh, after the dependency closures were rebuilt:\n - @objectstack/lint 148 files / 6934 tests passed;\n - @objectstack/metadata-protocol 248 passed, 3 skipped / 20733 passed, 19 skipped;\n - @objectstack/objectql 445 passed, 2 failed / 8407 passed, 7 failed on the first run. 6 were protocol-duplicate-package-names-in-step.test.ts (re-trued below: 6/6), and 1 is protocol-duplicate-package-unprefixed-object.test.ts "a name already claimed…", left red and raised as an open question (below). At a0c24ab647 the conformance, names-in-step and unprefixed files read 25 passed, 1 failed (that one);\n - @objectstack/rest 300 / 6095 passed; @objectstack/runtime 423 / 6655 passed; @objectstack/core 106 / 2580; @objectstack/metadata 63 / 892; @objectstack/platform-objects 78 / 1188; @objectstack/plugin-security 188 / 3754; @objectstack/service-automation 192 / 2567; @objectstack/service-cluster 7 / 105; @objectstack/service-datasource 45 / 838; @objectstack/cli (both projects) 474 passed, 1 skipped / 6018 passed;\n - @objectstack/dogfood: the 7 files the grep names plus the re-trued view-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); a git grep for package-scoped view writes (meta/view…?package=) in packages/qa/dogfood/test names only the re-trued file;\n - @objectstack/spec was not run: it has no workspace dependency (pg-connection-string, zod), so no spec test can reach @objectstack/lint. Its grep hits are prose.\n- Typechecks: lint (with check:test-typecheck), metadata-protocol, objectql and dogfood exit 0.\n- Ablation (the seat's ask), on the patch with the severity set back: severity: 'error' became severity: String(object) === 'ablated_24563_warning' ? 'error' : 'warning' through scripts/ablation-replace.mjs (anchor 1 → 0, blob f38af8f08988 → 377fdb85d3dd), lint rebuilt, marker in 4 built files.\n - Lint file: 4 red, 23 green. The reds were the three refusal cases and the object-door refusal inside the flow case. The CLI case stayed green because it checks the rule id only.\n - Protocol block: 5 red ((a) ×2, (d), (e), (g)), 5 green (the draft save, (b) ×2, (c), (f)). Exactly the refusal legs went red; every control held.\n - Restore: blob == HEAD, git diff HEAD empty, rebuild, --absent from all 20 built files, tree clean; lint file 27/27, protocol file 162/162.\n - The ablation ran on the patch's lint source (blob f38af8f08988, unchanged in a0c24ab647) before the fixture files were folded into the same commit.\n\n### Round 0\n\n"
    },
    {
    "old_str": "## Gates\n\nThese were run on the merge head 8a295d8477, which is the PR head.\n",
    "new_str": "## Gates\n\n### Patch round 1 (head a0c24ab647)\n\n- node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 95 families from 12 changed paths against merge base c3954057f. Each ran verbatim with its exit code recorded before any pipe.\n - --ran: "95 derived famil(ies) accounted for — 94 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.\n - The derivation warned STALE TREE: 3 derivation inputs (scripts/check-stack-collection-maps.mjs, scripts/engine-double-contract.pinned.json, scripts/platform-object-tenancy-census.json) changed on main since 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.\n- First runs that were not 0, and their re-runs:\n - check-engine-split-ratio --days 90 exited 2, the shallow-clone refusal. After git fetch --shallow-since=2026-07-14 origin main it exited 0;\n - check:skill-examples exited 3 (client-react had no .d.ts). After building client and client-react it exited 0 ("262 prose examples type-check");\n - check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET) and is NOT MEASURED, left to CI.\n- check-adr-0087-registration --base origin/main exit 0 ("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition"); check-empty-changeset --base origin/main exit 0; check-changeset-no-major --base origin/main exit 0 ("This diff introduces no major bump").\n- check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias and check:engine-double-contract: OK.\n- Not local: test:repo and pnpm lint (CI-owned).\n\n### Round 0\n\nThese were run on the merge head 8a295d8477.\n"
    },
    {
    "old_str": "- 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 names the one rule that is told the closure is in force.",
    "new_str": "- 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.\n\n## Fixtures re-trued (patch round 1), assertions unchanged\n\nEach 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.\n\n- packages/objectql/src/publish-package-drafts-response-conformance.test.ts (round 0's CI red, Test Core (5/6)). The views bound case, which exists nowhere in that harness. Six cases were refused under B: plain batch, probes, materializer, advisories, and two invariants. byte-stability passed only vacuously (an empty published[]). viewBody now binds sys_metadata, a platform object the harness registers, with its declared id column. The view element stays advisory-free beside the flow that carries the advisory. 16/16.\n- 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 declares dependencies: { [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.\n- packages/qa/dogfood/test/view-container-cross-package-default.dogfood.test.ts. The Studio-created com.example.repairassets saved containers for showcase_task without declaring the showcase. Its POST /packages manifest now declares dependencies: { [SHOWCASE]: '*' }. The container is still for an object another package ships, which is the test's subject. 4/4, and without the declaration it reads 422 / refused (P1 above).\n\nThese are three more files outside the claim's surface: the engine lane (packages/objectql) and packages/qa/dogfood. The seat adds them to the rider note on #6367.\n\nNot re-trued (open question in the report): packages/objectql/src/protocol-duplicate-package-unprefixed-object.test.ts, "a name already claimed is refused up front, at the object, naming the rename". There the SOURCE's view (ticket_views, bound to its own ticket) is inside its closure. The refused object copy leaves the COPY's view bound to the source's ticket, outside the copy's closure, and that copy is now refused: failed = the ticket object refusal plus view/ticket_views … views[0].object [list-view-object-outside-closure], against the asserted length 1. No fixture change re-trues it. It needs either the assertion re-pinned to the two refusals or a duplicatePackage change (engine lane, protocol.ts), so it is red at this head."
    },
    {
    "old_str": "## Acceptance notes\n\n- A write that names no package and binds an object that exists nowhere is still accepted silently at the view door: overlay nosuch_obj answers 200 with advisories: [], pinned as unchanged here. A flow on the same overlay write gets flow-trigger-unknown-object. This PR keeps the overlay door as the dispatch ruled. The reach of this case was measured through saveMetaItem over a stub engine, not through a booted HTTP door.\n- The two door verdicts for an out-of-closure object reference disagree (P2: flow advisory, dataset refusal). This view follows the flow's.\n- list-view-object-outside-closure has no os explain entry. The CLI never emits it, and the hint carries the remedy.\n- The first commit's subject reads fix(lint)!:. It was written before G3 was settled and is left as is (no force-push). The PR title is the squash subject.\n",
    "new_str": "## Acceptance notes\n\n- A write that names no package and binds an object that exists nowhere is still accepted silently at the view door: overlay nosuch_obj answers 200 with advisories: [], pinned as unchanged here (out of scope by the seat's record). A flow on the same overlay write gets flow-trigger-unknown-object. The seat files it at adoption if its reach holds.\n- The three doors now give two verdicts for an out-of-closure object reference: flow advisory; dataset and view refused.\n- list-view-object-outside-closure has no os explain entry. The CLI never emits it, and the hint carries the remedy.\n- An object written into a package whose built-in listViews entry retargets data.object outside the closure is refused too (pinned in the lint block's flow case). No shipped object retargets (P3).\n- Whether Studio's package editor can declare dependencies was not measured. POST /packages accepts them (the dogfood fixture above).\n"
    }
    ],
    "pr_title_edit": {
    "old": "fix(lint): a list view written into a package and bound outside its closure is reported at the save door, not skipped",
    "new": "fix(lint)!: the save door refuses a list view written into a package and bound outside its closure"
    },
    "open_questions": [
    {
    "question": "objectql protocol-duplicate-package-unprefixed-object.test.ts, 'a name already claimed is refused up front, at the object, naming the rename', asserts duplicatePackage reports exactly one failure. Under the refusal it reports two. The ticket object copy is refused by design, so the copy of the source's view ticket_views stays bound to the source's ticket, outside the copy package's closure, and is refused with list-view-object-outside-closure. The source fixture is inside its own closure, so no fixture change re-trues it. Per the addendum it is left red and not edited. Which way?",
    "options": [
    "A — re-pin the assertion to the two refusals: the ticket object refusal, then 'view/ticket_views' with list-view-object-outside-closure at views[0].object. Every other assertion stays, including 'everything else still copies'. Business need: a duplicate no longer lands a view in the copy bound to the SOURCE package's object; before, it landed with an advisory and showed the source's records. Long-term: the closure rule holds on the copy door as on every other door, with no exception. AI-safety: the partial copy is loud about the dependent view instead of shipping a cross-package binding. Startup focus: a test-only edit in one engine-lane file, which needs the #6367 rider.",
    "B — change duplicatePackage so a view whose object copy was refused is reported as not attempted (skipped, naming the refused object) rather than gated. Business need: the same end state, with a sharper message. Long-term: a second policy in the copy door. AI-safety: equivalent. Startup focus: a protocol.ts change in the engine lane, blocked while PR #24599 is in flight; more scope.",
    "C — exempt duplicatePackage's copies from the closure verdict. Rejected on every axis: it special-cases the rule (the patch instruction forbids it) and lets a copy carry exactly the binding the ruling refuses."
    ],
    "recommendation": "A, because the refusal is the ruled behaviour on this door too. The test's own subject, the object refused up front and naming the rename, is unchanged, and the extra failure is an accurate consequence the test should pin rather than hide. It is one test-file edit; say the word and it rides this PR's next commit."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: save door, measured through saveMetaItem over the stub engine (not a booted HTTP door). An overlay write (no package) of a list view bound to 'nosuch_obj' answers success with advisories []. Unchanged here and pinned as unchanged; the seat's record keeps it out of scope and files it at adoption if its reach holds. · dedupe words: overlay list view nonexistent object accepted, view data.object unresolved save door, list-view skip 1 overlay",
    "carrier: the seat's record 6273946447 (finding b) · noted, not filed: after this patch the flow door advises on an out-of-closure object reference while the dataset and view doors refuse it.",
    "carrier: none · noted, not filed (Acceptance notes only): whether Studio's package editor can declare manifest dependencies was not measured; POST /packages accepts them (the re-trued dogfood fixture). Under the refusal that is the only way a Studio-created package can hold a view over another package's object."
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24563,
    "status": "done",
    "branch": "claude/issue-24563-list-view-out-of-closure",
    "pr": "#24630",
    "head": "483b3ec995165f3b5b7511d7f2052ea1b30780b1",
    "session": "session_019oFY6Hi35J7atvgVFnmoaK — mode:subagent, the seat's id (claim 6271302258, amended 6273958360)",
    "premise_still_valid": true,
    "summary": "Patch round 2 is one commit, 483b3ec on a0c24ab, taking seat answer A (6277565064). In objectql protocol-duplicate-package-unprefixed-object.test.ts, the case 'a name already claimed is refused up front, at the object, naming the rename' now pins res.failed to exactly two entries, in order. The first is the ticket object refusal, with every existing assertion on it. The second is { type: 'view', name: 'ticket_views' }, whose error carries the locator 'views[0].object [list-view-object-outside-closure]' (rule id and path, not the message). A one-line comment says why the view is refused: the ticket copy was refused, so the copy keeps the source's ticket, outside the copy package's closure. The negative control and 'everything else still copies' are unchanged. The changeset's FROM and TO lines now name duplicatePackage beside saveMetaItem publish, the draft promotion and publishPackageDrafts, in the seat's wording. The docs sentence in validating-metadata.mdx lists no doors, so it is unchanged. files_changed (this round): packages/objectql/src/protocol-duplicate-package-unprefixed-object.test.ts, .changeset/24563-list-view-out-of-closure.md. deviations: (1) The changeset's FROM line also names duplicatePackage; the instruction named the TO line only. (2) The worktree was re-created on the existing branch; the earlier cleanup had removed it. (3) The branch was not re-merged. No file this PR touches changed on main (now cc95f5e), and dispatch-gates still warns STALE TREE for 3 derivation inputs. (4) The PR body was not written. pr_body_edits is ONE consolidated set of 18 old/new pairs against the LIVE body (still byte-identical to round 0), each old_str verified to occur exactly once. The set covers rounds 1 and 2 (Clause-② yes (narrowing), refused in place of reported/advised, duplicatePackage in the door list, the re-pin in place of the fixture left red, P1/P3 readings, and Verification and Gates for the final head). pr_title_edit is unchanged from round 1.",
    "tests": "At 483b3ec, each run under os-verify-lock.sh after rebuilding the objectql dependency closure (BUILD exit 0): objectql protocol-duplicate-package-unprefixed-object.test.ts 'Tests 4 passed (4)'; pnpm --filter @objectstack/objectql test --maxWorkers=2 'Test Files 447 passed (447) / Tests 8414 passed (8414)', VERDICT command-exit 0; lint runtime-gate.package-closure.test.ts 'Tests 27 passed (27)'; metadata-protocol protocol.runtime-authoring-gate.test.ts 'Tests 162 passed (162)'. The rest of the consumer sweep was not re-run, per the instruction (only objectql's result changes); the round-1 sweep readings stand. CI confirms the red this round fixes: on a0c24ab, Test Core (5/6) failed on @objectstack/objectql src/protocol-duplicate-package-unprefixed-object.test.ts (job log read), the case re-pinned here. No ablation this round: the diff changes a test's assertions and changeset text, not tested behaviour.",
    "gates": "At 483b3ec: check-adr-0087-registration --base origin/main exit 0 ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'); check-changeset-no-major --base origin/main exit 0 ('This diff introduces no major bump'); check-empty-changeset --base origin/main exit 0, all captured before any pipe. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 95 families from 13 changed paths vs merge base c395405. That is identical to round 1's 95 (diff of the sorted lists empty), so round 2's paths add no family. All 95 were re-run verbatim on this head anyway. First non-zero runs, re-run after building their prerequisite: check:skill-examples exit 3 and check:lean-entry-closure exit 3 → built client, client-react and objectql → both exit 0. NOT MEASURED: check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (needs every package built), left to CI. --ran: '✓ dispatch-gates --ran: 95 derived famil(ies) accounted for — 94 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', exit 0. Not local: test:repo and pnpm lint (CI-owned). PR check-runs on 483b3ec at report time: 7 success, 5 skipped, 26 in_progress, 0 failure.",
    "mcp_calls": "0",
    "api_writes": "1 this round: one relay stroke, POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot] → comment, POST /repos//issues/24563/comments (this report). Plus 1 git push of the branch (a0c24ab..483b3ec, not REST). No PR body or title write, no label write, no rider note.",
    "pr_body_edits": [
    {
    "old_str": "Fixes #24563\n\nClause-②: yes\n\n",
    "new_str": "Fixes #24563\n\nClause-②: yes (narrowing)\n\n"
    },
    {
    "old_str": "- A binding outside the closure is reported, never skipped. This covers an object of a package the writing package never declared, and an object that exists nowhere. It gets one list-view-object-outside-closure finding at warning, located at the key the binding is read from (views[0].data.object). The hint names the remedy: declare a dependency on the package that defines the object in the dependencies of the writing package's manifest. That is the verdict a flow's out-of-closure trigger object gets at the same door (P2), so the write is still stored and the finding rides the 2xx advisories.",
    "new_str": "- A binding outside the closure is refused, never skipped. This covers an object of a package the writing package never declared, and an object that exists nowhere. A publish-mode write (an active save, a draft promotion, a package draft publish) answers 422 INVALID_METADATA with one list-view-object-outside-closure issue at error, 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 the dependencies of 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. duplicatePackage writes 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."
    },
    {
    "old_str": "## Read first: the declaration differs from the claim\n\nThe claim (6271302258) and the dispatch carry Clause-②: no (narrowing). That line was written when the out-of-closure binding was expected to be refused. P2 measured a flow's out-of-closure object reference at advisory severity. The dispatch's own prohibition says that in that case "the view gets the same advisory for the binding, and the in-closure field refusal stays as it is". So this PR refuses nothing that was accepted before: no accept set narrows. The public surface does grow, by an optional AuthoringRuleContext.packageScope, an optional second argument to validateListViewFieldRefs, and two new exports (LIST_VIEW_OBJECT_OUTSIDE_CLOSURE, ListViewFieldRefOptions). That is Clause-②: yes by the definition "does this card widen the accept set or enlarge the public surface". So this body and the changeset declare Clause-②: yes at minor, with no !, no FROM / TO lines and no ADR-0087 disposition. The claim's line is the seat's to correct, or this body's if the seat rules otherwise.\n\n",
    "new_str": "## Patch rounds 1 and 2: refused, not advised\n\nThe seat answered the round-0 question with B (record 6273946447 on this PR) and amended the claim's line to Clause-②: yes (narrowing) (6273958360 on #24563). list-view-object-outside-closure is now an error, so the door narrows: the binding the round-0 head reported is refused. The public surface still grows by an optional AuthoringRuleContext.packageScope, an optional second argument to validateListViewFieldRefs, and two exports (LIST_VIEW_OBJECT_OUTSIDE_CLOSURE, ListViewFieldRefOptions). Widen plus narrow is Clause-②: yes (narrowing). The changeset is minor with a ! title, FROM / TO / Fix lines and the ADR-0087 disposition not-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). The duplicatePackage case whose copied view now stays outside the copy's closure is re-pinned to the two refusals, and the changeset's door list names duplicatePackage.\n\n"
    },
    {
    "old_str": "a binding missing from stack.objects is reported once per list view, at the key it was read from",
    "new_str": "a binding missing from stack.objects is refused once per list view (error), at the key it was read from"
    },
    {
    "old_str": " - out-of-closure binding reported, nosuch_obj reported, and reported under an empty closure;",
    "new_str": " - out-of-closure binding refused, nosuch_obj refused, and refused under an empty closure;"
    },
    {
    "old_str": " - the flow verdict unchanged, with a stored sibling's out-of-closure list view cancelling in the differential and reported when that object is itself written;",
    "new_str": " - the flow verdict unchanged, with a stored sibling's out-of-closure list view cancelling in the differential (a flow publish is not refused for it) and refused when that object is itself written;"
    },
    {
    "old_str": " - (a) the active save is reported, for the stranger's object and for nosuch_obj;",
    "new_str": " - (a) the active save is refused 422 INVALID_METADATA at views[0].data.object, nothing stored, for the stranger's object and for nosuch_obj; the draft save of the same body is not gated;"
    },
    {
    "old_str": " - (d) draft promotion: reported, and the in-closure case refused;\n - (e) package draft publish: reported on published[].advisories, and the in-closure case refused;\n - (f) a view bound to an object drafted in the same batch: no phantom report, and its fields judged.",
    "new_str": " - (d) draft promotion: refused, nothing stored, and the in-closure case refused;\n - (e) package draft publish: refused (failed[0], INVALID_METADATA, the closure issue), nothing stored, and the in-closure case refused;\n - (f) a view bound to an object drafted in the same batch: no phantom refusal, and its fields judged;\n - (g) a stored view with the binding loads (loaded: 1, errors: 0); its next save into the package is refused and the row is untouched."
    },
    {
    "old_str": "- Changeset: .changeset/24563-list-view-out-of-closure.md, @objectstack/lint minor, Clause-②: yes.",
    "new_str": "- Changeset: .changeset/24563-list-view-out-of-closure.md, @objectstack/lint minor, ! title, Clause-②: yes (narrowing), FROM / TO / Fix lines (FROM and TO name saveMetaItem publish, the draft promotion, publishPackageDrafts and duplicatePackage), the stored-row sentence, and the ADR-0087 disposition not-required (no-migration-prescription) (check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition")."
    },
    {
    "old_str": "- After (this branch):\n - stranger's object: RESOLVED success=true state=active advisories=[[\"list-view-object-outside-closure\",\"warning\",\"views[0].data.object\"]];\n - nosuch_obj: the same advisory;\n - overlay: unchanged (422, and nosuch_obj there 200 with advisories=[]);\n - publishPackageDrafts: published=1 with published[0].advisories=[[\"list-view-object-outside-closure\",\"warning\",\"views[0].data.object\"]];\n - in-closure controls: unchanged.",
    "new_str": "- After (patch round 1, a0c24ab647), through the protocol block on the same harness:\n - stranger's object: refused 422 INVALID_METADATA, issues=[[\"list-view-object-outside-closure\",\"error\",\"views[0].data.object\"]], no active row;\n - nosuch_obj: the same refusal;\n - overlay: unchanged (422 list-view-field-unknown, and nosuch_obj there 200 with advisories=[]);\n - publishPackageDrafts: success=false publishedCount=0, failed[0] INVALID_METADATA with the closure issue;\n - in-closure controls: unchanged.\n- After, through the booted HTTP door: the dogfood file view-container-cross-package-default.dogfood.test.ts (real showcase composition, REST) run with its Studio-created package declaring NO dependency answered expected 422 to be 200 on PUT /api/v1/meta/view/os_qa_keyed_probe?package=com.example.repairassets and expected 'refused' to be 'published' on POST /packages/com.example.repairassets/publish-drafts. With the dependency declared it passes 4/4.\n- (Round 0, 8a295d8477, for the record: the same probe answered RESOLVED success=true with the finding at warning in advisories.)"
    },
    {
    "old_str": "So the door has two verdicts today for "an object outside the package's closure": flow advisory, dataset refusal. Per the dispatch the view takes the flow's. The question is raised in the report.",
    "new_str": "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."
    },
    {
    "old_str": "- Control through the same harness: the CRM lead view retargeted at showcase_task, under com.example.crm, draws 3 advisories (list and its two listViews) and 0 errors.",
    "new_str": "- Control through the same harness: the CRM lead view retargeted at showcase_task, under com.example.crm, draws 3 advisories (list and its two listViews) and 0 errors at round 0.\n- Re-run at patch round 1 (a0c24ab647, universe 119 objects after main added sys_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."
    },
    {
    "old_str": " - its ladder answers error for an unprefixed unresolved name. Package scope, out-of-closure: [\"error:object-reference-unknown@views[0].object\"]. That is a refusal, not the P2 verdict;",
    "new_str": " - its ladder answers error for an unprefixed unresolved name but warning for 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\"];"
    },
    {
    "old_str": "- G3. Falsified in part, by P2. The out-of-closure binding is advisory and the in-closure refusal already existed, so no accept set narrows.\n - The changeset is minor on the public-surface growth, with Clause-②: yes.\n - There is no !, no FROM / TO, and no ADR-0087 marker: check-adr-0087-registration --base origin/main reads no breaking declaration.\n - The Fix line is there: declare the dependency, or bind inside the closure, after which the field is judged.\n",
    "new_str": "- 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 manifest dependencies, or bind an object it may reach), following PR #24509's changeset. check-adr-0087-registration --base origin/main decides not-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.)\n"
    },
    {
    "old_str": "## Verification\n\n",
    "new_str": "## Verification\n\n### Patch rounds 1 and 2 (final head 483b3ec995)\n\n- Two commits on 8a295d8477: a0c24ab647 (round 1) and 483b3ec995 (round 2: the duplicatePackage re-pin and the changeset door list). There is no merge, because no file this PR touches changed on main (now cc95f5e63e).\n- Round 2, at 483b3ec995, each run under os-verify-lock.sh after rebuilding the objectql dependency closure:\n - pnpm --filter @objectstack/objectql test --maxWorkers=2: 447 files / 8414 tests passed;\n - protocol-duplicate-package-unprefixed-object.test.ts: 4/4;\n - lint runtime-gate.package-closure.test.ts: 27/27;\n - metadata-protocol protocol.runtime-authoring-gate.test.ts: 162/162.\n - Only objectql's result changes in round 2, so the rest of the sweep was not re-run (the seat's instruction).\n- Round 1 consumer sweep, at the a0c24ab647 tree. It covered every package whose tests save or publish through the gate (git grep -l \"publishPackageDrafts\\|saveMetaItem\" -- \"packages/**/*.test.ts\"). Each ran pnpm --filter PKG test --maxWorkers=2 under os-verify-lock.sh:\n - @objectstack/lint 148 files / 6934 tests passed;\n - @objectstack/metadata-protocol 248 passed, 3 skipped / 20733 passed, 19 skipped;\n - @objectstack/objectql 445 passed, 2 failed / 8407 passed, 7 failed. 6 were protocol-duplicate-package-names-in-step.test.ts (re-trued, 6/6) and 1 was the duplicatePackage case re-pinned in round 2;\n - @objectstack/rest 300 / 6095 passed; @objectstack/runtime 423 / 6655 passed; @objectstack/core 106 / 2580; @objectstack/metadata 63 / 892; @objectstack/platform-objects 78 / 1188; @objectstack/plugin-security 188 / 3754; @objectstack/service-automation 192 / 2567; @objectstack/service-cluster 7 / 105; @objectstack/service-datasource 45 / 838; @objectstack/cli (both projects) 474 passed, 1 skipped / 6018 passed;\n - @objectstack/dogfood: the 7 files the grep names plus the re-trued view-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). A git grep for package-scoped view writes (meta/view…?package=) in packages/qa/dogfood/test names only the re-trued file;\n - @objectstack/spec was 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.\n- Typechecks (round 1): lint (with check:test-typecheck), metadata-protocol, objectql and dogfood exit 0.\n- Ablation (round 1, the seat's ask): the severity was set back to warning. severity: 'error' became severity: String(object) === 'ablated_24563_warning' ? 'error' : 'warning' through scripts/ablation-replace.mjs (anchor 1 → 0, blob f38af8f08988 → 377fdb85d3dd). Lint was rebuilt, and the marker was in 4 built files.\n - Lint file: 4 red, 23 green. The reds were the three refusal cases and the object-door refusal in the flow case. The CLI case checks the rule id only and stayed green.\n - Protocol block: 5 red ((a) ×2, (d), (e), (g)) and 5 green (the draft save, (b) ×2, (c), (f)). Exactly the refusal legs went red, and every control held.\n - Restore: blob == HEAD, git diff HEAD empty, rebuild, --absent from all 20 built files, tree clean. The lint file passed 27/27 and the protocol file 162/162.\n - The lint source (blob f38af8f08988) is unchanged in 483b3ec995.\n\n### Round 0\n\n"
    },
    {
    "old_str": "## Gates\n\nThese were run on the merge head 8a295d8477, which is the PR head.\n",
    "new_str": "## Gates\n\n### Final head 483b3ec995\n\n- node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 95 families from 13 changed paths against merge base c3954057f, 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.\n - --ran: "95 derived famil(ies) accounted for — 94 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.\n - The derivation warns STALE TREE: 3 derivation inputs (scripts/check-stack-collection-maps.mjs, scripts/engine-double-contract.pinned.json, scripts/platform-object-tenancy-census.json) changed on main since 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.\n- First runs that were not 0, re-run after building their prerequisite:\n - check:skill-examples (client-react had no .d.ts) and check:lean-entry-closure (objectql/dist/core absent) each exited 3. After building client, client-react and objectql, both exited 0.\n - check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET). It is NOT MEASURED and left to CI.\n - Round 1's check-engine-split-ratio shallow refusal is cleared by the deepened clone; it exits 0 here.\n- check-adr-0087-registration --base origin/main exit 0 ("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition"); check-changeset-no-major --base origin/main exit 0 ("This diff introduces no major bump"); check-empty-changeset --base origin/main exit 0.\n- Not local: test:repo and pnpm lint (CI-owned).\n\n### Round 0\n\nThese were run on the merge head 8a295d8477.\n"
    },
    {
    "old_str": "- 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 names the one rule that is told the closure is in force.",
    "new_str": "- 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.\n\n## Fixtures re-trued (patch round 1), assertions unchanged; one case re-pinned (patch round 2)\n\nEach 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.\n\n- packages/objectql/src/publish-package-drafts-response-conformance.test.ts (round 0's CI red, Test Core (5/6)). The views bound case, which exists nowhere in that harness. Six cases were refused under B: plain batch, probes, materializer, advisories, and two invariants. byte-stability passed only vacuously (an empty published[]). viewBody now binds sys_metadata, a platform object the harness registers, with its declared id column. The view element stays advisory-free beside the flow that carries the advisory. 16/16.\n- 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 declares dependencies: { [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.\n- packages/qa/dogfood/test/view-container-cross-package-default.dogfood.test.ts. The Studio-created com.example.repairassets saved containers for showcase_task without declaring the showcase. Its POST /packages manifest now declares dependencies: { [SHOWCASE]: '*' }. The container is still for an object another package ships, which is the test's subject. 4/4, and without the declaration it reads 422 / refused (P1 above).\n\nThese are files outside the claim's surface: the engine lane (packages/objectql) and packages/qa/dogfood.\n\nRe-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 view ticket_views is inside its own closure. Because the ticket copy is refused, the copy of that view keeps the source's ticket, which is outside the copy package's closure, so the copy is refused with the same rule.\n- res.failed is pinned to exactly two entries, in order: the ticket object refusal, with every existing assertion on it; then { type: 'view', name: 'ticket_views' }, whose error carries views[0].object [list-view-object-outside-closure] (the rule id and path, not the message).\n- The negative control and "everything else still copies" are unchanged, and a one-line comment says why the view is refused. 4/4.\n\nThe riders are declared by the seat: the three objectql test files on #6367 (6277575090), and the dogfood test on #6024 (6277594189)."
    },
    {
    "old_str": "## Acceptance notes\n\n- A write that names no package and binds an object that exists nowhere is still accepted silently at the view door: overlay nosuch_obj answers 200 with advisories: [], pinned as unchanged here. A flow on the same overlay write gets flow-trigger-unknown-object. This PR keeps the overlay door as the dispatch ruled. The reach of this case was measured through saveMetaItem over a stub engine, not through a booted HTTP door.\n- The two door verdicts for an out-of-closure object reference disagree (P2: flow advisory, dataset refusal). This view follows the flow's.\n- list-view-object-outside-closure has no os explain entry. The CLI never emits it, and the hint carries the remedy.\n- The first commit's subject reads fix(lint)!:. It was written before G3 was settled and is left as is (no force-push). The PR title is the squash subject.\n",
    "new_str": "## Acceptance notes\n\n- A write that names no package and binds an object that exists nowhere is still accepted silently at the view door: overlay nosuch_obj answers 200 with advisories: [], pinned as unchanged here (out of scope by the seat's record). A flow on the same overlay write gets flow-trigger-unknown-object. The seat files it at adoption if its reach holds.\n- The three doors now give two verdicts for an out-of-closure object reference: flow advisory; dataset and view refused.\n- list-view-object-outside-closure has no os explain entry. The CLI never emits it, and the hint carries the remedy.\n- An object written into a package whose built-in listViews entry retargets data.object outside the closure is refused too (pinned in the lint block's flow case). No shipped object retargets (P3).\n- Whether Studio's package editor can declare dependencies was not measured. POST /packages accepts them (the dogfood fixture above).\n"
    }
    ],
    "pr_title_edit": {
    "old": "fix(lint): a list view written into a package and bound outside its closure is reported at the save door, not skipped",
    "new": "fix(lint)!: the save door refuses a list view written into a package and bound outside its closure"
    },
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: save door, measured through saveMetaItem over the stub engine (not a booted HTTP door). An overlay write (no package) of a list view bound to 'nosuch_obj' answers success with advisories []. Unchanged and pinned as unchanged; the seat's record keeps it out of scope and files it at adoption if its reach holds. · dedupe words: overlay list view nonexistent object accepted, view data.object unresolved save door, list-view skip 1 overlay",
    "carrier: the seat's record 6273946447 (finding b) · noted, not filed: the flow door advises on an out-of-closure object reference while the dataset and view doors refuse it.",
    "carrier: none · noted, not filed (Acceptance notes only): whether Studio's package editor can declare manifest dependencies was not measured; POST /packages accepts them."
    ]
    }


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24563,
    "status": "done",
    "branch": "claude/issue-24563-list-view-out-of-closure",
    "pr": "#24630",
    "head": "700c21cbbd4a1bfd7af1c6ce1872ecb578fa4821",
    "session": "session_019oFY6Hi35J7atvgVFnmoaK — mode:subagent, the seat's id (claim 6271302258, amended 6273958360)",
    "premise_still_valid": true,
    "summary": "One commit, 700c21c on 483b3ec. In .changeset/24563-list-view-out-of-closure.md, the BREAKING paragraph's parenthetical '(.changeset/pre.json is open in next pre mode)' becomes '(.changeset/pre.json is in exit mode, so the next version pass cuts 18.0.0 and this change ships in it)'. The rest of that sentence and the rest of the file are unchanged (diff: 1 insertion, 1 deletion). The reading was measured first: git show origin/main:.changeset/pre.json reads {"mode": "exit", "tag": "next"} at origin/main cc95f5e, and git show 483b3ec995:.changeset/pre.json reads the same. c46b5f1 (#24555) is an ancestor of both (merge-base --is-ancestor exit 0 on each, a self-proving reading). files_changed (this round): .changeset/24563-list-view-out-of-closure.md. deviations: (1) The worktree was re-created on the existing branch and needed pnpm install. The first check-adr-0087-registration run exited 1 with 'no parser-rot witness: the witness generator's runner tsx is not installed', a prerequisite and not a verdict; after the install it exited 0. (2) pr_body_edits is unchanged from round 2. Its Verification and Gates sections name 483b3ec as the verified head; this round adds only the changeset-prose commit 700c21c on top, which no test or derived family reads differently.",
    "tests": "No test changed this round (changeset prose only); round-2 readings at 483b3ec stand: objectql 447/8414 passed, lint pin 27/27, metadata-protocol pin 162/162. No ablation: no tested behaviour changed.",
    "gates": "At 700c21c, each exit captured before any pipe: node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 ('✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.'); node scripts/check-changeset-no-major.mjs --base origin/main exit 0 ('✓ This diff introduces no major bump.'); node scripts/check-empty-changeset.mjs --base origin/main exit 0 ('✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).'); pnpm check:nul-bytes exit 0 before the push. dispatch-gates was not re-derived this round: the change is one prose line in a file already in round 2's change set. PR check-runs on 700c21c: not read yet (just pushed).",
    "mcp_calls": "0",
    "api_writes": "1 this round: one relay stroke, POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot] → comment, POST /repos//issues/24563/comments (this report). Plus 1 git push (483b3ec..700c21c, not REST). No PR body, title or label write.",
    "pr_body_edits": "unchanged from round 2 — the live PR body (re-read in this act, byte-identical to round 0) does not quote the pre.json parenthetical (0 occurrences of 'pre.json' or 'pre mode'), and neither does any round-2 new_str; the round-2 set (report comment 6278185241, 18 pairs) applies as is.",
    "pr_title_edit": {
    "old": "fix(lint): a list view written into a package and bound outside its closure is reported at the save door, not skipped",
    "new": "fix(lint)!: the save door refuses a list view written into a package and bound outside its closure"
    },
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T23:03Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:specpriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions