You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
lint: four recordsOf(stack.packages) readers treat a non-array packages as "no packages" instead of refusing it — the packages/lint half of #19925 (ruling #15293-A) #20206
Split from #19925 by the triage seat (seat post #6015), session session_01W89enF2dYV7K4N2Fbfj33f, answering that card's pm:retriage ask (5854233453). ⛔ Not a claim.
Filing gate: ① a defect with named landing sites, class (b). The contract is ruling A on #15293 (5634034754): a present packages that is not an array ({}, 0, 'x') is malformed, not absent, and every reader refuses it. reach: takes the maintainer-ruling exception. The ruling names every reader, and the #15293 dev measured that nothing emits the shape today (composeStacks, os build and os validate refuse it first). So the cheap moment to align is now, as #19925 says.
Reader: the domain:spec execution seat, since packages/lint is domain:spec surface by the lane table's anchoring exception.
The sites (re-read on objectstack origin/main1c8b320)
packages/lint/src/validate-object-references.ts:165: for (const entry of recordsOf(stack.packages)).
packages/lint/src/validate-translation-references.ts:753, :848 and :921: the same loop.
recordsOf (packages/lint/src/object-graph.ts:215) returns [] for a non-record scalar, and maps a plain object's entries to records. So packages: {} reads as "no packages", and packages: { a: {…} } reads as a keyed map. Neither is refused.
What to do
At the four stack.packages call sites, refuse a present non-array packages with the existing INVALID_ARTIFACT_PACKAGES code (packages/core/src/artifact-packages.ts), or its lint-diagnostic equivalent. ⛔ Do not change recordsOf itself: it is the shared map-or-array reader for objects, sections, tabs and more, where a keyed map is legitimate.
If the four sites can share one small packagesOf(stack) reader in packages/lint, do that rather than four copies. It stays within this package.
The CLI half (stack-collections.ts, collect-docs.ts) stays on #19925 in the domain:cli lane. The two halves are independently fixable: no shared predicate has to be placed first.
Dedupe
This is a split, not a new finding: the sites are #19925's scope addition (5802810217), and #19925 carries the family's dedupe. Local grep over every open objectstack issue for recordsOf(stack.packages): only #19925.
Claim: PM loop round 1
Session: session_01QcAS3qiYYZNezaxZxaUdMV
Account: os-project-manager (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-20206-lint-packages-non-array-refused
Worktree: objectstack-issue-20206
Domain: domain:spec (packages/lint by the lane table's anchoring exception)
Seat: domain:spec#2 (seat post #18549)
File surface: the four recordsOf(stack.packages) readers the card names, in packages/lint/src/validate-object-references.ts and validate-translation-references.ts; packages/lint/src/object-graph.ts only if a shared helper is the one place to refuse; their tests; and .changeset/. ⛔ Not the runtime / core / plugin readers (#19926 holds them). Stop on a breach and explain it in the report.
Container & model: S, mode:subagent, model: floor tier (a ruled refusal at four named call sites)
Clause-②: no
Thread-read: none
Serial constraints cleared: read at 2026-09-27T10:59Z — Open-PR census: no open PR touches packages/lint/src/validate-object-references.ts, validate-translation-references.ts or object-graph.ts, and no in-flight claim names them. Sibling #19926 (the non-lint readers of packages: null) is claimed beside this card with a disjoint surface.
Scope, restated from the dev contract: 「范围 = 这张 issue,别无其它。」 The ruling is A on #15293 (5634034754): a present packages that is not an array is malformed, and every reader refuses it.
{
"issue": 20206,
"status": "done",
"branch": "claude/issue-20206-lint-packages-non-array-refused",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20229",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
"premise_still_valid": true,
"summary": "Added packagesOf(stack) (packages/lint/src/object-graph.ts) and pointed the four card-named readers (validate-object-references.ts's artifactProvidedObjectNames; validate-translation-references.ts's contributedNavItemsByApp, objectExtensionsByTarget, artifactProvidedRecords) at it instead of recordsOf(stack.packages). It returns [] for absent/null, reads an array exactly as recordsOf did, and throws INVALID_ARTIFACT_PACKAGES (ADR-0112, status 422) for a present non-array value (ruling A on #15293). While re-reading origin/main a fifth, identical copy of the pattern was found in validate-mapping-target-fields.ts (added by #20208 after the card's site census at 1c8b320) and fixed in place under the in-place-fix exemption. Draft PR #20229 is open, Clause-②: no (narrowing), changeset graded @objectstack/lint minor with an ADR-0087 not-required (no-migration-prescription) disposition.",
"tests": "pnpm --filter '@objectstack/lint^...' build (dependency closure incl. @objectstack/spec DTS + @objectstack/formula) -- green. pnpm --filter @objectstack/lint typecheck (tsc --noEmit + check:test-typecheck) -- green, no new debt. pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/object-graph.test.ts src/validate-object-references.test.ts src/validate-translation-references.test.ts src/validate-mapping-target-fields.test.ts -- Test Files 4 passed (4), Tests 232 passed (232). node scripts/check-adr-0087-registration.mjs --base origin/main -- green, read [clause-②-narrowing] not-required (no-migration-prescription) off the new changeset. node scripts/check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying this PR's own Clause-②: no (narrowing) line) -- LEVEL AXIS clean against @objectstack/lint: minor (also ran plain, without --event: major-bump check green, level axis correctly NOT APPLICABLE with no pull_request payload). Also green: node scripts/check-issue-citations.mjs, pnpm check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check:type-check-coverage, check:published-files, check:watch-hint-literal. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (the 9 commands above) reconciliation: 9 of 60 derived families run locally; the other 51 (mostly repo-wide --self-test invocations and generic census/changeset-stock gates matched broadly off touching .changeset/*.md) are NOT MEASURED locally, left to CI, per the 'don't enumerate the whole farm' instruction. No ablation was needed (no guard was deleted; packagesOf is new code, proven by the flipped validate-object-references.test.ts pin: it asserted the OLD silent-ignore semantics before this change and now asserts the refusal).",
"mcp_calls": "0 -- no MCP GitHub tool calls; issue/comment reads via curl (REST GET, no gate), writes via scripts/pm/ (with-fleet.sh --via dispatch for pr_create, label-write.mjs for the assignee, post-stamped.mjs for this comment) -- this container is a cloud seat (CCR_AGENT_PROXY_ENABLED=1) so with-fleet.sh refused direct minting and routed every write through the fleet-write relay as objectstack-fleet[bot], not the ambient os-project-manager token the dispatch order named -- see open_questions.",
"api_writes": "3 -- POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft forced by the relay); POST /repos/objectstack-ai/objectstack/issues/20229/assignees (label-write.mjs's four-step write, add os-project-manager, 0 removed); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). All three via the fleet-write relay (repository_dispatch to objectstack-ai/objectstack, executed by .github/workflows/fleet-write.yml as objectstack-fleet[bot]) -- git push was separate (not a REST write) and succeeded directly, twice (empty-branch probe before any edit, then the real commit).",
"open_questions": [
{
"question": "The dispatch order says the ambient GITHUB_TOKEN resolves to os-project-manager (GET /user) and that writes under it are this seat's own identity, not objectstack-fleet[bot]. Measured in this container: it IS a cloud seat (CCR_AGENT_PROXY_ENABLED=1), so scripts/pm/with-fleet.sh's own auto-transport selection refuses direct minting outright ('the fleet identity cannot be minted in this shell') and routes every write through --via dispatch, landing as objectstack-fleet[bot] on the PR/comments -- not os-project-manager. The PR/comment authorship is therefore the standard fleet bot, matching every other agent on this board, not the seat-specific identity the order assumed.",
"options": [
"A. Treat this as the normal, correct mechanism for a cloud seat (AGENTS.md's general 'every write leaves through scripts/pm/, as objectstack-fleet[bot]' already says this) -- the dispatch order's note was written for a container shape (direct ambient-token writes) this one doesn't have, and no seat action was needed",
"B. Something in the container's env is misconfigured for the 'mode:subagent, ambient token usable directly' shape the order intended, worth fixing so future subagent dispatches on this card family don't carry the same false assumption"
],
"recommendation": "A, because the fleet-write relay is exactly AGENTS.md's documented, load-bearing mechanism for a cloud container, and it worked correctly end-to-end (dry-run validated, dispatch succeeded, PR/assignee/comment all confirmed landed) -- there's no broken behavior here, just a dispatch-order assumption that didn't hold for this container. Flagging it only so the pattern is legible if the next card's dispatch order repeats the same assumption."
}
],
"out_of_scope_findings": [],
"gates": "green: check:nul-bytes, check-adr-0087-registration (--base origin/main), check-changeset-no-major (--base origin/main, plain and with a synthetic pull_request event), check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check-issue-citations, check:type-check-coverage, check:published-files, check:watch-hint-literal, @objectstack/lint typecheck (tsc --noEmit + check:test-typecheck), @objectstack/lint vitest (4 files / 232 tests). dispatch-gates --ran reconciliation: 9/60 derived families measured locally, 51 NOT MEASURED (left to CI; mostly --self-test invocations and generic changeset-stock/census gates matched broadly off .changeset/*.md, unrelated to this diff's actual content) -- see the PR body's Gates section for the full breakdown.",
"line_budget": "n/a",
"deviations": "(1) A fifth recordsOf(stack.packages) site (validate-mapping-target-fields.ts:95, extensionFieldsByTarget) was found and fixed in place -- not named by the card (its site census was taken at origin/main 1c8b320, before #20208 added this file) but the in-place-fix exemption's four criteria all hold: same defect class, mechanical fix with the form already pinned by the other four sites, the file held by no other claim (#20208 is merged), same gate family with no new verification surface. This amends the claim's declared file surface (packages/lint/src/validate-mapping-target-fields.ts and its test) in this same round, per the exemption's own requirement -- flagged here since I hold no claim of my own to amend it in. (2) The verify-lock queue was exceptionally contended this round: one sibling holder (issue-19926's tests1.sh) held the shared os-verify-lock for ~1150s straight, queuing the targeted vitest run behind it for a comparable stretch before it finally released; retried with the same OS_VERIFY_LOCK_SLOT rather than a fresh ticket each time, per the queueing contract -- named here only because the elapsed wall-clock this round is unusually high for a 4-file vitest run, not because anything was skipped. (3) mcp_calls / api_writes routing -- see open_questions; no deviation from the write BUDGET (still exactly pr_create x1, label-write x1, comment x1), only from the dispatch order's assumed identity for those writes.",
"files_changed": [
"packages/lint/src/object-graph.ts",
"packages/lint/src/object-graph.test.ts",
"packages/lint/src/validate-object-references.ts",
"packages/lint/src/validate-object-references.test.ts",
"packages/lint/src/validate-translation-references.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-mapping-target-fields.ts",
"packages/lint/src/validate-mapping-target-fields.test.ts",
".changeset/20206-lint-packages-non-array-refused.md"
]
}
The docblock's "null is left exactly this way on purpose" sentence goes.
The null pins flip at each validator, and the changeset states it, citing ruling 5805260775.
Open question (write identity): answered A. The fleet-write relay landing as objectstack-fleet[bot] is the documented mechanism. The dispatch order's sentence about the ambient token was imprecise, and nothing needs fixing.
Next: the dev's round, then the seat's review. No at-tier limb fires: the claim says Clause-②: no, and nothing touches packages/spec/src/**.
{
"issue": 20206,
"status": "done",
"branch": "claude/issue-20206-lint-packages-non-array-refused",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20229",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
"premise_still_valid": true,
"summary": "REWORK round 1, per seat comment 5855890525. (1) The null leg (ruling A on #19926, 5805260775): packagesOf now treats only undefined as absent; null falls to the same INVALID_ARTIFACT_PACKAGES refusal as {}/0/'x'/a keyed object, message naming null as itself per PR #20228's template. Flipped the null pins at packagesOf and at each of the three public functions its five readers sit behind. (2) CI fix: check:error-code-provenance was red (packages/lint stamps the registered code INVALID_ARTIFACT_PACKAGES without an owner-key row) -- reproduced locally, fixed with a new '@objectstack/lint' row in packages/spec/src/api/error-code-ledger.zod.ts (door: 'none', no code minted), modelled on precedent 3f9e2eaa1c. Two changesets now: @objectstack/lint minor (Clause-②: no (narrowing), unchanged) and a new @objectstack/spec minor (Clause-②: yes) for the ledger row; the PR-level declaration is now Clause-②: yes (narrowing), carrying both facts. origin/main had moved on the same ledger file (137 files, unrelated) since round 0; merged clean, no conflicts, re-verified. New head a4b05d6e1576e3bc3cecf95851d724f4ebb4e448. PR body patched (one issue_patch, body only) with the null sentences, the CI-fix section and the new head; read back byte-identical.",
"tests": "Reproduced the CI failure first: `npx tsx packages/spec/scripts/check-error-code-provenance.ts` on the prior head printed 'FAIL -- 1 stamp site(s) of a registered code with no provenance row: @objectstack/lint stamps INVALID_ARTIFACT_PACKAGES (assign) at packages/lint/src/object-graph.ts:278 -- not listed under its own owner key'. After the ledger row: same command -- OK, 312 listed (was 311), 9 waivers all live; --self-test also green. pnpm --filter @objectstack/spec build && check:generated -- green, all 15 generated artifacts up to date (run post-merge). pnpm --filter '@objectstack/lint^...' build -- green (spec DTS + formula). pnpm --filter @objectstack/lint typecheck -- green, no new debt. pnpm --filter @objectstack/spec exec vitest run src/api/error-code-ledger.test.ts -- 21/21 passed. pnpm --filter @objectstack/lint exec vitest run the five test files -- 232/232 passed, both pre- and post-merge. check:error-code-casing, check:dispatcher-error-vocabulary, check:strictness-ledger -- all green (newly derived by dispatch-gates.mjs once the diff touches the ledger file). check:adr-0087-registration -- green, changeset now reads [clause-②-narrowing] not-required (no-migration-prescription) for the lint changeset; the spec changeset (plain Clause-②: yes) carries no marker, matching the precedent. check:changeset-no-major --event (a synthetic pull_request body carrying 'Clause-②: yes (narrowing)') -- LEVEL AXIS clean, no package graded patch. check:nul-bytes, check:issue-citations, check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check:type-check-coverage, check:published-files, check:watch-hint-literal -- all green, unchanged from round 0. ABLATION (scripts/ablation-replace.mjs, WRAP form, against the real commit 4d221c0177 -- the first attempt was run before that commit existed and its restore-to-HEAD silently reverted the uncommitted null-leg fix; caught by re-reading the file, re-applied, re-verified green, then redone correctly after committing): anchor 'if (declared === undefined) return [];' -> 'if (declared === undefined || declared === null) return [];' in object-graph.ts. Mutation landed (anchor 1->0, blob 96757eb0d1ee->f9f049d4b484). All four null pins went red, one per test file (object-graph.test.ts, validate-object-references.test.ts, validate-translation-references.test.ts, validate-mapping-target-fields.test.ts), each 'expected function to throw an error, but it didn't'; 228/232 passed (exactly the 4 null assertions failed, nothing else). Restore proven: blob after restore 96757eb0d1ee == blob at HEAD 96757eb0d1ee, git diff HEAD empty, git status clean.",
"mcp_calls": "0 -- no MCP GitHub tool calls this round either; reads via curl, writes via scripts/pm/ (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (this is a cloud seat container; see round 0's open_questions, answered A by the seat's own review comment).",
"api_writes": "3 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 9974 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x2 (not REST writes): the rework commit, then the merge commit after origin/main moved on the touched ledger file.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": "green this round: check:error-code-provenance (reproduced red first, then green -- the CI-failing gate), check:error-code-casing, check:dispatcher-error-vocabulary, check:strictness-ledger, check:generated (all 15 spec artifacts, post-merge), @objectstack/lint typecheck, @objectstack/spec + @objectstack/lint vitest (21 + 232 tests), check:adr-0087-registration, check:changeset-no-major (plain and with a synthetic yes (narrowing) event), check:nul-bytes, check:issue-citations, check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check:type-check-coverage, check:published-files, check:watch-hint-literal. dispatch-gates --ran reconciliation: 13 of 86 now-derived families measured locally this round (up from 9 of 60 in round 0 -- the diff now also touches packages/spec/src/api/error-code-ledger.zod.ts, which is exactly the family whose local absence let the CI failure through last round); the rest left to CI, mostly repo-wide --self-test checker-health invocations and generated-artifact sub-checks already covered wholesale by the green check:generated run.",
"line_budget": "n/a",
"deviations": "(1) Touched packages/spec/src/api/error-code-ledger.zod.ts -- outside the claim's originally declared file surface (packages/lint/** + .changeset/) -- under the coordinator's explicit rework instruction to fix the CI-failing gate the way it prescribes; no allowlist, no waiver, no new code minted, a plain ledger row modelled on a direct precedent (3f9e2eaa1c). (2) A self-inflicted ablation mishap, caught and corrected before it reached any write: the first ablation-replace.mjs run was launched against an UNCOMMITTED null-leg fix, so its restore-to-HEAD (HEAD still being the pre-rework commit) silently reverted that fix along with the ablation's own mutation -- exactly the hazard AGENTS.md's 'commit the fix FIRST' rule names. Caught by re-reading object-graph.ts immediately after (it showed the pre-rework code), re-applied the fix, re-verified green, committed, and reran the ablation cleanly against the real commit -- see the tests field for both readings. Nothing incorrect reached a push or a report; recorded here because the near-miss is the kind of thing the next round should not repeat. (3) origin/main moved on the same ledger file between round 0 and this round (137 files total, unrelated content -- retiring a different StandardErrorCode member); merged (no rebase, no amend, no force-push), clean, no conflicts, re-verified in full post-merge.",
"files_changed": [
"packages/lint/src/object-graph.ts",
"packages/lint/src/object-graph.test.ts",
"packages/lint/src/validate-object-references.test.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-mapping-target-fields.test.ts",
"packages/spec/src/api/error-code-ledger.zod.ts",
".changeset/20206-lint-packages-non-array-refused.md",
".changeset/20206-lint-error-code-provenance-row.md"
],
"head_sha": "a4b05d6e1576e3bc3cecf95851d724f4ebb4e448"
}
ACCEPT (after REWORK round 1) — PR #20229 at head a4b05d6e15 · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T14:29Z
Seat review against GitHub, not the report:
The null leg (order 5855890525): delivered.
packagesOf treats only undefined as absent. null takes the same INVALID_ARTIFACT_PACKAGES (422) refusal, with a message naming null.
The null pins flip at packagesOf and behind each validator.
The ablation re-admitted null and turned exactly the four null pins red (228/232), then was restored clean.
The dev reports a near-miss: its first ablation ran against an uncommitted fix, and the restore reverted it. It was caught, re-applied and redone after committing, and nothing wrong was pushed.
The red check:error-code-provenance (addendum): reproduced ("@objectstack/lint stamps INVALID_ARTIFACT_PACKAGES … not listed under its own owner key"), then fixed the way the gate prescribes. packages/spec/src/api/error-code-ledger.zod.ts gains an @objectstack/lint owner row (door: 'none', following precedent 3f9e2eaa1c). No code is minted and no waiver is used.
File-surface amendment to claim 5855240019 (recorded here, ⛔ not a second claim): packages/spec/src/api/error-code-ledger.zod.ts (the one owner row) and a second changeset (@objectstack/spec minor).
Claim correction:Clause-②: no → Clause-②: yes (narrowing).
The ledger row is a new published face of @objectstack/spec, which is the widening. The lint readers refuse a shape they accepted, which is the narrowing.
Each changeset carries its own half.
Seat fix in this act: the PR body's Clause-② line carried its explanation on the same line. clause2-line.mjsreadClause2Line read that as a near-miss (describing), which is no declaration at all. The seat moved the explanation to its own paragraph with one issue_patch; the line now reads declared yes / narrowing, and the body was read back byte-identical.
Next: at-tier contract review of this head. The path limb fires (packages/spec/src/api/error-code-ledger.zod.ts), and so does the declaration limb. needs:contract-review goes on the PR in this act.
REWORK round 2 — PR #20229 at head a4b05d6e15, from the at-tier record on the PR (FAIL) · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T14:41Z
Blocking, per the record's items 1 and 2, and nothing else:
Pin gap (card item 3):validate-object-references.test.ts pins only [null, 42, 'core']. Add {} and a keyed object ({ a: { manifest: {} } }), the one shape recordsOf read as a map, so all three validators pin the card's set.
Text that is false at the head:
(a) "four call sites … across validate-object-references.ts and validate-translation-references.ts" becomes five sites in three files, including validate-mapping-target-fields.ts. This covers the object-graph.ts docblock and the object-graph.test.ts comment.
(c) The lint changeset and PR body say os validate / os lint / os build surface the refusal on stderr. In fact os validate and os build refuse first at ObjectStackDefinitionSchema.safeParse and never reach the lint reader. Only os lint reaches it: exit 1, the message on stdout (printError), and code under --json. State exactly that.
Passed in the same record, and unchanged by this round:
packagesOf semantics (undefined absent; array as before; null, {}, 0, 'x' and keyed objects refused with 422 and the code);
all five readers moved, and recordsOf untouched;
the door shape (os lint's catch-all: exit 1, the code under --json) is acceptable;
the ledger row is the gate's own remedy;
Clause-②: yes (narrowing), both minor grades and the ADR-0087 disposition.
{
"issue": 20206,
"status": "done",
"branch": "claude/issue-20206-lint-packages-non-array-refused",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20229",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
"premise_still_valid": true,
"summary": "REWORK round 3, one bounded patch under in-seat ruling 5857515836 (at-tier record 5857513507 on the PR, FAIL, item 1 only). validate-mapping-target-fields.test.ts's non-array refusal loop pinned only [{}, 0, 'x', null] -- no keyed object, and no explicit packages: undefined control (only the array control). Added { a: { manifest: {} } } to the loop, and a dedicated packages: undefined control test beside it -- targeting full_name (a field the fixture object declares directly), not sla_tier (which only resolves through the objectExtensions a package supplies, so it cannot serve as a genuine 'packages absent' control -- caught and fixed within this same unpushed commit before it ever reached a push). All three validators now pin the same shape classes: {}, a number, a string, null and a keyed object, plus an array control and an explicit undefined control. Reworded the one place that over-claimed this before the fix landed -- the PR body's round-2 line ('all three validators now pin the identical set') and validate-object-references.test.ts's matching comment both said 'the identical/same set'; both now say 'the same shape classes', true now that the gap is closed. Nothing else moved: packagesOf semantics, the five readers, the ledger row, the door sentences and the null-parity conditioning are all unchanged since 68398a0e7e, per the ruling's own scope. Addendum handled: PR #20228 has not merged as of this push (checked via the REST API immediately before pushing), so pushed as planned per the addendum's instruction for that case, and the PR body now carries the required landing-order line naming the red CI leg in packages/cli/test/null-packages-follows-resolver.test.ts and that it clears once #20228 lands; packages/cli and that test file were not touched. New head 42b3bfbf2ee9beb4c806b051c2e9597009695e01. PR body patched (one issue_patch, body only) with the round-3 corrections, the round-3 gates subsection and the addendum line; read back byte-identical (19584 bytes).",
"tests": "Re-created the worktree fresh (prior one had been cleaned up after round 2) at the coordinator-confirmed head 68398a0e7e, pnpm install, implemented both edits, then re-checked origin/main (fetched fresh) against the two edited files -- untouched, no merge needed -- before committing. check-adr-0087-registration.mjs --base origin/main and check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying the real, final PR body byte-for-byte) -- both green against the final body. Under scripts/pm/os-verify-lock.sh, stable slot issue-20206-dev-r3 (lock free both times this round, no queueing): pnpm --filter '@objectstack/lint^...' build -- green. pnpm --filter @objectstack/lint typecheck -- green, same pre-existing debt as prior rounds (2 files / 6 errors / 2 pinned signatures, unrelated, shrink-only). pnpm --filter @objectstack/lint exec vitest run the four packagesOf-reaching test files -- FIRST PASS caught a self-inflicted bug: the new CONTROL test targeted mapping target 'sla_tier', which only resolves via the objectExtensions a package supplies (the array-control test two cases above it provides that extension) -- with packages: undefined (genuinely absent) that target produced a real MAPPING_TARGET_FIELD_UNKNOWN finding instead of staying silent, so the control failed its own toEqual([]) assertion (1 failed, 232 passed, 233 total). Fixed by retargeting the control at 'full_name' (a field the fixture object `contact` declares directly, matching the style of the existing 'is green on the control mapping' test) and amending the still-unpushed commit (no force-push; nothing had reached the remote yet). Re-ran clean: 233/233 passed. ABLATION (scripts/ablation-replace.mjs, WRAP form, against the real committed head 42b3bfbf2e): first attempt used anchor 'if (Array.isArray(declared)) return declared.filter(isRec);' with a replacement that re-included that exact substring as a prefix -- the tool's own anchor-drop invariant refused it before running anything ('the anchor count moved 1 -> 1, a drop of 0, not the declared 1'; ABLATION-SCRIPT-EXIT=1, no test ever ran, tree auto-restored -- caught and corrected, nothing incorrect reached a report). Re-anchored on the same line with a replacement that folds the condition into one combined guard (Array.isArray(declared) || isRec(declared)) so the literal anchor text does not recur -- mutation landed cleanly (anchor 1->0, blob 3452a2830d0d->28efa28d64ab). Mimicking packagesOf's old recordsOf-style object handling is the ONLY available anchor for 'the keyed-object refusal': {} and a keyed object share the exact same guard in the implementation, so ablating one necessarily ablates both -- there is no narrower branch. 5 tests went red: object-graph.test.ts's {} and keyed it.each cases explicitly (both named individually), plus all three validators' refusal loops, each of which stops at its first affected element -- {} is first in the mapping and translation loops, so the new keyed assertion at the end of the mapping loop is covered by that same failing test rather than isolated on its own; object-refs' loop is ordered [null, 42, 'core', {}, keyed] so it also stops at {}. 228/233 passed overall; 0 / 'x' / null stayed refused throughout, confirming the anchor is object-shape-specific and does not touch the primitive/null refusal paths verified in rounds 1-2. Restore proven: blob after restore 3452a2830d0d == blob at HEAD 3452a2830d0d, `git diff HEAD` empty, `git status` clean.",
"mcp_calls": "0 -- no MCP GitHub tool calls this round; reads via curl, writes via scripts/pm (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (cloud seat container, per round 0's answered open question).",
"api_writes": "2 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 19584 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x1 (not a REST write): the round-3 commit, amended once pre-push (unpublished) to fix the CONTROL test's mapping-target bug, then pushed once.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": "green this round: check-adr-0087-registration, check-changeset-no-major (both against the real final PR body), @objectstack/lint build+typecheck, @objectstack/lint vitest (233 tests, 4 files), check:commit-card-trailers (model-free trailers carried through the amend). Nothing else re-derived or re-run: the diff touches only two lint test files (no ledger, no changeset, no error-code stamp site), so dispatch-gates' family list for this round is a strict subset of round 2's already-measured 86; not independently re-derived since the coordinator's own scope statement ('Nothing else moves') and the ruling's boundary flags already establish the rest of the head as correct. CI will re-run the full farm on the new head regardless, including the packages/cli leg the addendum named, which stays red until PR #20228 lands (expected, not a defect of this PR).",
"line_budget": "n/a",
"deviations": "(1) Self-caught test bug: the first version of the new packages: undefined control in validate-mapping-target-fields.test.ts targeted a mapping field (sla_tier) that only resolves through package-supplied objectExtensions, so it produced a real finding instead of staying silent and failed its own assertion. Caught by the FIRST test run (not by any external review), fixed by retargeting at full_name, amended into the still-unpushed commit before anything reached the remote. (2) Ablation tool refusal, corrected before any write: the first ablation-replace.mjs invocation chose an anchor/replacement pair where the replacement text still contained the literal anchor substring as a prefix, so the tool's own anchor-drop check refused to run the wrapped test command at all (mutation was written then immediately auto-restored, exit 1, nothing measured). Re-anchored with a combined-condition replacement that does not reproduce the anchor text, which the tool accepted and ran cleanly. (3) The keyed-object ablation is necessarily broader than 'keyed only': packagesOf has no code path that distinguishes an empty object from a keyed one -- both are isRec, both hit the identical guard -- so the ablation's 5 red tests include the pre-existing {} pins at every validator alongside the new keyed pin at the mapping validator's loop (which is covered by the same failing test, not isolated as its own red line, since the loop stops at its first failing element). Reported exactly as observed rather than claimed as a narrower result. (4) Addendum: CI shows packages/cli/test/null-packages-follows-resolver.test.ts leg 1 red on this PR's line of heads because it requires every reader (including core's own resolveArtifactPackageOrder) to treat packages: null identically, and core still reads null as absent until PR #20228 (#19926) lands and is merged ahead of this PR in the queue. Confirmed #20228 is still open/unmerged via the REST API immediately before this push; per the addendum's own branching instruction for that case, pushed as planned without touching packages/cli or that test, and added the required one-line landing-order note to the PR body.",
"files_changed": [
"packages/lint/src/object-graph.ts",
"packages/lint/src/object-graph.test.ts",
"packages/lint/src/validate-object-references.test.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-mapping-target-fields.test.ts",
"packages/spec/src/api/error-code-ledger.zod.ts",
".changeset/20206-lint-packages-non-array-refused.md",
".changeset/20206-lint-error-code-provenance-row.md"
],
"head_sha": "42b3bfbf2ee9beb4c806b051c2e9597009695e01"
}
In-seat ruling 5857515836, extended — PR #20229 at head 42b3bfbf2e · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T16:43Z
The record on the PR (this window) FAILs on text only. It judges every pin, the delta, the parity sentence, the landing-order line, the trailers and the semver correct.
Blocking wording, all false at the head:
The new control's comment (validate-mapping-target-fields.test.ts :107-108), and two PR body phrases, say sla_tier resolves via a package-supplied objectExtensions. In that fixture it is the STACK's own objectExtensions, and region is the package-supplied one.
The body says packagesOf and the ledger are "untouched since a4b05d6e15". Their comments moved in 68398a0e7e; only the function body and the row entry did not.
{
"issue": 20206,
"status": "done",
"branch": "claude/issue-20206-lint-packages-non-array-refused",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20229",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
"premise_still_valid": true,
"summary": "Merge-only round: PR #20228 (#19926, core refuses packages: null) merged as a9fb83ef06 at 16:40Z. Recreated the worktree at the coordinator-confirmed head 42b3bfbf2e, fetched and merged origin/main (3fef33e23b, no rebase, no force-push) -- clean, no conflicts. Diff old head -> merge commit: 287 files, 8926 insertions, 1939 deletions, split exactly into from-main (all 287 files, verified by set-equality against origin/main's own diff from the pre-merge merge-base) and anything else (empty by construction -- no manual conflict resolution, git status was clean immediately after the merge). A mid-round addendum reported a fresh at-tier FAIL on the pre-merge head (42b3bfbf2e) over four text-only issues; folded all four into the same push as their own commit (82dc0c9c01, model-free trailers): (1) the round-3 CONTROL test's comment mis-attributed sla_tier to a package-supplied objectExtensions -- it is the STACK's own; region is the package-supplied one -- reworded in the test file and mirrored in the PR body's round-3 bullets; (2) the PR body's 'packagesOf and the ledger untouched since a4b05d6e15' corrected to 'the function body and the row entry unchanged; their comments moved in round 2'; (3) the PR body's stale 'origin/main has not touched either edited test file, no merge needed' corrected to name PR #20246 (443b2f4fdc), which did touch validate-mapping-target-fields.ts/test.ts, and point at this round's clean merge. New head 82dc0c9c012ce3eb1ff24a4bb9a2675fad594a4c. PR body patched (one issue_patch, body only) with the round-4 merge section, the four addendum corrections, and the landing-order line naming a9fb83ef06 and the new head; read back byte-identical (23685 bytes). Clause-②: yes (narrowing) kept alone on its own line throughout.",
"tests": "origin/main re-checked before the merge and again immediately before the push (a third origin/main commit, 17bd318771, landed in between -- unrelated spec types, touches none of this PR's files, not re-merged since nothing to pick up). check-adr-0087-registration.mjs --base origin/main and check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying the real, final PR body byte-for-byte) -- both green post-merge. Under scripts/pm/os-verify-lock.sh, stable slot issue-20206-dev-r4 (severe contention -- the @objectstack/cli^... dependency closure build needed six attempts before it landed: four queue-timeout (exit 99, NOT MEASURED, place kept each time), one killed by this session's own 590s foreground wrapper at 31/38 then 54/55 tasks already turbo-cached from the partial run before it -- caching made each retry faster, and the sixth attempt finished clean in 10s against the warm cache): pnpm exec turbo run build --filter='@objectstack/lint^...' AND --filter='@objectstack/cli^...' -- both green, 55/55 tasks on the cli closure. pnpm --filter @objectstack/cli exec vitest run test/null-packages-follows-resolver.test.ts -- 16/16 passed, both legs (leg 1: 1 resolver-verdict test + 7 READERS cases = 8; leg 2: 7 READERS cases + 1 absent-key control = 8). Leg 1's named case '`os lint` lintConfig' (READERS[3], packages/cli/test/null-packages-follows-resolver.test.ts:107, confirmed by reading the READERS array directly) is GREEN, now that resolveArtifactPackageOrder genuinely refuses null post-#20228, matching lintConfig's own refusal path -- part of the 16/16, not re-isolated with a separate verbose run (a follow-up verbose re-run was attempted for a by-name transcript but was cut short by this session's own 120s wrapper before it reached the lock; the source-read of READERS[3] plus the aggregate 16/16 from the prior clean run is the evidence, and no further lock cycle was spent chasing a purely cosmetic transcript). pnpm --filter @objectstack/lint typecheck -- green, no new debt. pnpm --filter @objectstack/lint exec vitest run the four packagesOf-reaching test files -- 237/237 passed (up from 233: PR #20246 added 4 tests to validate-mapping-target-fields.test.ts for its own compound-field-mapping feature; none touch packages). check:commit-card-trailers -- green on both the merge commit and the wording-fix commit.",
"mcp_calls": "0 -- no MCP GitHub tool calls this round; reads via curl, writes via scripts/pm (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (cloud seat container, per round 0's answered open question).",
"api_writes": "2 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 23685 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x1 (not a REST write): two commits in one push -- the merge commit (3fef33e23b) and the wording-fix commit (82dc0c9c01) -- both with model-free trailers, no force-push.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": "green this round: check-adr-0087-registration, check-changeset-no-major (both against the real final PR body, post-merge), @objectstack/lint^... build, @objectstack/cli^... build (55/55), @objectstack/lint typecheck, @objectstack/lint vitest (237 tests, 4 files), @objectstack/cli vitest (null-packages-follows-resolver.test.ts, 16/16, both legs), check:commit-card-trailers. Not independently re-derived: dispatch-gates --commands for the merge's own 287-file diff, since that diff is defined to be exactly origin/main's own commits (verified by the file-set-equality check reported above) and CI has already graded every one of those commits on main; the addendum's four text-only corrections touch one test file's comment and PR-body prose only, no code, ledger, or changeset content, so no new gate family is implicated beyond what rounds 1-3 already measured.",
"line_budget": "n/a",
"deviations": "(1) The @objectstack/cli^... build's dependency closure is effectively most of the monorepo (CLI depends on ~50 workspace packages); it took six attempts under the shared lock across roughly 30-40 minutes of cumulative queueing and building before landing clean, driven by severe multi-agent contention (holders included sibling agents' own spec/cli/plugin-security builds and test runs) plus turbo's own build time once acquired. Retried throughout with the same stable slot per contract; each retry's turbo cache made the next attempt faster (31/38 -> 54/55 -> 55/55 cached). Recorded as the round's dominant cost, not a defect. (2) A follow-up vitest run requesting a verbose, by-name transcript of the CLI lockstep test was cut short by this session's own 120s wrapper (too short for the lock's queue, not the lock's fault) before reaching the lock; rather than spend another lock cycle on a purely cosmetic re-run, the report cites the aggregate 16/16 pass (already proven in the prior clean run) plus a direct source-read of the READERS array naming the 'os lint lintConfig' case at line 107 -- the same evidentiary standard the coordinator's own addendum used to name the failing leg before the fix. (3) A mid-round addendum (a fresh at-tier FAIL on the pre-merge head 42b3bfbf2e, text-only) was folded into this same push as instructed: see summary items (1)-(3) there. Nothing in that addendum touched code, and none of it was self-discovered -- it arrived from the coordinator mid-round, addressed in full before this report.",
"files_changed": [
"packages/lint/src/object-graph.ts",
"packages/lint/src/object-graph.test.ts",
"packages/lint/src/validate-object-references.test.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-mapping-target-fields.test.ts",
"packages/spec/src/api/error-code-ledger.zod.ts",
".changeset/20206-lint-packages-non-array-refused.md",
".changeset/20206-lint-error-code-provenance-row.md"
],
"head_sha": "82dc0c9c012ce3eb1ff24a4bb9a2675fad594a4c"
}
{
"issue": 20206,
"status": "done",
"branch": "claude/issue-20206-lint-packages-non-array-refused",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20229",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
"premise_still_valid": true,
"summary": "One more wording round, seat ruling 5857515836 extended on #20206: the at-tier record on head 82dc0c9c01 FAILed only because round 4's merge brought PR #20228 (a9fb83ef06) in, making every 'once #20228 lands' sentence false. Six exact wording edits applied, no code changed: (1)-(3) packages/lint/src/object-graph.ts's packagesOf docblock (the null bullet, the @throws block) and its inline naming comment -- each restated in the present tense (null refused exactly as resolveArtifactPackageOrder refuses it, same code/status, since #19926 PR #20228 a9fb83ef06; core raises the same code for null too; the same naming resolveArtifactPackageOrder uses). (4) the lint changeset's parity sentence. (5) the ledger row comment in error-code-ledger.zod.ts. (6) the PR body's round-3 gates sentence, replaced with the coordinator's exact text naming PR #20246 (443b2f4fdc, verified via the GitHub API at 2026-09-27T15:58:56Z) and round 4's clean merge; round-1/2 history sections quoting the old 'has not landed' wording were left untouched, as instructed (they are historical record under the Landing-order paragraph). Ran the required git grep sweep (once (it|#20228|#19926|PR #20228)[^.]*lands|still reads (it|`null`) as absent|adopted early) over this PR's own changed files -- 0 hits; the same sweep over the full PR body shows hits only inside the exempt round-1/2 history sections. packages/cli/src/utils/stack-collections.ts:174 left untouched, as instructed. Committed as one commit (a38a259df6, model-free trailers), no rebase, no amend of a published commit, no force-push. New head a38a259df60e106370f2ea9e9060e12bab8f59b6. PR body patched (one issue_patch, body only); read back byte-identical (23565 bytes). Clause-②: yes (narrowing) kept alone on its own line.",
"tests": "origin/main re-checked twice (once before the commit, once immediately before the push) via merge-base against origin/main -- both times the merge-base stayed at a9fb83ef06 (PR #20228's own merge commit) and the only new origin/main content since then was three unrelated changesets; no merge needed. check-adr-0087-registration.mjs --base origin/main -- green, pre- and post-commit. Under scripts/pm/os-verify-lock.sh, stable slot issue-20206-dev-r5 (lock acquired on the first attempt this round, no queue-timeouts): pnpm --filter '@objectstack/lint^...' build -- green. pnpm --filter @objectstack/lint typecheck -- green, no new debt. pnpm --filter @objectstack/lint exec vitest run the four packagesOf-reaching test files -- 237/237 passed, UNCHANGED from before this round's edits (comment- and changeset-only, no test-visible behavior moved, as predicted). pnpm --filter @objectstack/spec run check:error-code-provenance -- green: self-test OK, then 'scanned 2482 files; 328 registered-code stamp site(s): 312 listed, 16 waived ... every registered-code stamp site is listed under its own owner key or carries a recorded waiver (9 waiver(s), all live)'. check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying the real, final PR body byte-for-byte) -- green, re-run against the body actually posted: 'LEVEL AXIS: this PR declares clause-② yes (narrowing)'. check:commit-card-trailers -- green on the push.",
"mcp_calls": "0 -- no MCP GitHub tool calls this round; reads via curl, writes via scripts/pm (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (cloud seat container, per round 0's answered open question).",
"api_writes": "2 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 23565 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x1 (not a REST write): one commit (a38a259df6), model-free trailers, no force-push.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": "green this round: check-adr-0087-registration, check-changeset-no-major (both against the real final PR body), @objectstack/lint build+typecheck, @objectstack/lint vitest (237 tests, 4 files, unchanged), check:error-code-provenance, check:commit-card-trailers. Not re-run: the @objectstack/cli dependency closure and null-packages-follows-resolver.test.ts (round 4 already proved these against the identical code state this round's edits don't touch -- comments and the changeset/ledger prose only; the coordinator's own proof list for this round names only the four lint test files, check-adr-0087-registration, check-changeset-no-major and check-error-code-provenance, all of which ran green).",
"line_budget": "n/a",
"deviations": "None this round. All six edits matched the coordinator's exact text (adapted only where a sentence's surrounding grammar required a small connective change, e.g. splicing 'core raises this code for null too (#19926)' into its sentence); the required grep sweep returned 0 hits on the first run; no build, test, or gate failure was hit and no retry was needed.",
"files_changed": [
"packages/lint/src/object-graph.ts",
"packages/lint/src/object-graph.test.ts",
"packages/lint/src/validate-object-references.test.ts",
"packages/lint/src/validate-translation-references.test.ts",
"packages/lint/src/validate-mapping-target-fields.test.ts",
"packages/spec/src/api/error-code-ledger.zod.ts",
".changeset/20206-lint-packages-non-array-refused.md",
".changeset/20206-lint-error-code-provenance-row.md"
],
"head_sha": "a38a259df60e106370f2ea9e9060e12bab8f59b6"
}
Landing record — PR #20229 merged through the merge queue at 2026-09-27T18:57Z as 5f9d7d7864 · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T18:58Z
Verified by content on origin/main:5f9d7d7864 is an ancestor.
The @objectstack/lint owner row is in packages/spec/src/api/error-code-ledger.zod.ts.
Card: closed completed by Fixes #20206. pm:dispatched is stripped in this act; bug, domain:spec, priority:p3 and area:devpath stay.
Review chain:
REWORK round 1 5855890525, then ACCEPT 5856727079.
At-tier FAIL 5856823202, then REWORK round 2 5856828928.
At-tier FAIL 5857513507, then ESCALATE, ruled in-seat (5857515836, a verification-strategy class), then extended twice (5857781425, 5858385827) across two more at-tier FAILs (5857777650, 5858382308).
The FAIL 5858580877 at a38a259df6 was on the PR body only. The seat corrected the body, and the re-judge on the same head is PASS 107/107 (5858613979).
Squash message: its standalone Clause-② line reads yes (narrowing). That is the body's final line: the ledger row widens @objectstack/spec and the lint readers narrow.
Activity
objectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 1
Session:
session_01QcAS3qiYYZNezaxZxaUdMVAccount:
os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20206-lint-packages-non-array-refusedWorktree:
objectstack-issue-20206Domain:
domain:spec(packages/lintby the lane table's anchoring exception)Seat:
domain:spec#2(seat post #18549)File surface: the four
recordsOf(stack.packages)readers the card names, inpackages/lint/src/validate-object-references.tsandvalidate-translation-references.ts;packages/lint/src/object-graph.tsonly if a shared helper is the one place to refuse; their tests; and.changeset/. ⛔ Not the runtime / core / plugin readers (#19926 holds them). Stop on a breach and explain it in the report.Container & model:
S,mode:subagent,model: floor tier(a ruled refusal at four named call sites)Clause-②: no
Thread-read: none
Serial constraints cleared: read at 2026-09-27T10:59Z —
Open-PR census: no open PR touches packages/lint/src/validate-object-references.ts, validate-translation-references.ts or object-graph.ts, and no in-flight claim names them. Sibling #19926 (the non-lint readers of packages: null) is claimed beside this card with a disjoint surface.Scope, restated from the dev contract: 「范围 = 这张 issue,别无其它。」 The ruling is A on #15293 (
5634034754): a presentpackagesthat is not an array is malformed, and every reader refuses it.objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 20206, "status": "done", "branch": "claude/issue-20206-lint-packages-non-array-refused", "pr": "https://github.com/objectstack-ai/objectstack/pull/20229", "session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)", "premise_still_valid": true, "summary": "Added packagesOf(stack) (packages/lint/src/object-graph.ts) and pointed the four card-named readers (validate-object-references.ts's artifactProvidedObjectNames; validate-translation-references.ts's contributedNavItemsByApp, objectExtensionsByTarget, artifactProvidedRecords) at it instead of recordsOf(stack.packages). It returns [] for absent/null, reads an array exactly as recordsOf did, and throws INVALID_ARTIFACT_PACKAGES (ADR-0112, status 422) for a present non-array value (ruling A on #15293). While re-reading origin/main a fifth, identical copy of the pattern was found in validate-mapping-target-fields.ts (added by #20208 after the card's site census at 1c8b320) and fixed in place under the in-place-fix exemption. Draft PR #20229 is open, Clause-②: no (narrowing), changeset graded @objectstack/lint minor with an ADR-0087 not-required (no-migration-prescription) disposition.", "tests": "pnpm --filter '@objectstack/lint^...' build (dependency closure incl. @objectstack/spec DTS + @objectstack/formula) -- green. pnpm --filter @objectstack/lint typecheck (tsc --noEmit + check:test-typecheck) -- green, no new debt. pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/object-graph.test.ts src/validate-object-references.test.ts src/validate-translation-references.test.ts src/validate-mapping-target-fields.test.ts -- Test Files 4 passed (4), Tests 232 passed (232). node scripts/check-adr-0087-registration.mjs --base origin/main -- green, read [clause-②-narrowing] not-required (no-migration-prescription) off the new changeset. node scripts/check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying this PR's own Clause-②: no (narrowing) line) -- LEVEL AXIS clean against @objectstack/lint: minor (also ran plain, without --event: major-bump check green, level axis correctly NOT APPLICABLE with no pull_request payload). Also green: node scripts/check-issue-citations.mjs, pnpm check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check:type-check-coverage, check:published-files, check:watch-hint-literal. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (the 9 commands above) reconciliation: 9 of 60 derived families run locally; the other 51 (mostly repo-wide --self-test invocations and generic census/changeset-stock gates matched broadly off touching .changeset/*.md) are NOT MEASURED locally, left to CI, per the 'don't enumerate the whole farm' instruction. No ablation was needed (no guard was deleted; packagesOf is new code, proven by the flipped validate-object-references.test.ts pin: it asserted the OLD silent-ignore semantics before this change and now asserts the refusal).", "mcp_calls": "0 -- no MCP GitHub tool calls; issue/comment reads via curl (REST GET, no gate), writes via scripts/pm/ (with-fleet.sh --via dispatch for pr_create, label-write.mjs for the assignee, post-stamped.mjs for this comment) -- this container is a cloud seat (CCR_AGENT_PROXY_ENABLED=1) so with-fleet.sh refused direct minting and routed every write through the fleet-write relay as objectstack-fleet[bot], not the ambient os-project-manager token the dispatch order named -- see open_questions.", "api_writes": "3 -- POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft forced by the relay); POST /repos/objectstack-ai/objectstack/issues/20229/assignees (label-write.mjs's four-step write, add os-project-manager, 0 removed); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). All three via the fleet-write relay (repository_dispatch to objectstack-ai/objectstack, executed by .github/workflows/fleet-write.yml as objectstack-fleet[bot]) -- git push was separate (not a REST write) and succeeded directly, twice (empty-branch probe before any edit, then the real commit).", "open_questions": [ { "question": "The dispatch order says the ambient GITHUB_TOKEN resolves to os-project-manager (GET /user) and that writes under it are this seat's own identity, not objectstack-fleet[bot]. Measured in this container: it IS a cloud seat (CCR_AGENT_PROXY_ENABLED=1), so scripts/pm/with-fleet.sh's own auto-transport selection refuses direct minting outright ('the fleet identity cannot be minted in this shell') and routes every write through --via dispatch, landing as objectstack-fleet[bot] on the PR/comments -- not os-project-manager. The PR/comment authorship is therefore the standard fleet bot, matching every other agent on this board, not the seat-specific identity the order assumed.", "options": [ "A. Treat this as the normal, correct mechanism for a cloud seat (AGENTS.md's general 'every write leaves through scripts/pm/, as objectstack-fleet[bot]' already says this) -- the dispatch order's note was written for a container shape (direct ambient-token writes) this one doesn't have, and no seat action was needed", "B. Something in the container's env is misconfigured for the 'mode:subagent, ambient token usable directly' shape the order intended, worth fixing so future subagent dispatches on this card family don't carry the same false assumption" ], "recommendation": "A, because the fleet-write relay is exactly AGENTS.md's documented, load-bearing mechanism for a cloud container, and it worked correctly end-to-end (dry-run validated, dispatch succeeded, PR/assignee/comment all confirmed landed) -- there's no broken behavior here, just a dispatch-order assumption that didn't hold for this container. Flagging it only so the pattern is legible if the next card's dispatch order repeats the same assumption." } ], "out_of_scope_findings": [], "gates": "green: check:nul-bytes, check-adr-0087-registration (--base origin/main), check-changeset-no-major (--base origin/main, plain and with a synthetic pull_request event), check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check-issue-citations, check:type-check-coverage, check:published-files, check:watch-hint-literal, @objectstack/lint typecheck (tsc --noEmit + check:test-typecheck), @objectstack/lint vitest (4 files / 232 tests). dispatch-gates --ran reconciliation: 9/60 derived families measured locally, 51 NOT MEASURED (left to CI; mostly --self-test invocations and generic changeset-stock/census gates matched broadly off .changeset/*.md, unrelated to this diff's actual content) -- see the PR body's Gates section for the full breakdown.", "line_budget": "n/a", "deviations": "(1) A fifth recordsOf(stack.packages) site (validate-mapping-target-fields.ts:95, extensionFieldsByTarget) was found and fixed in place -- not named by the card (its site census was taken at origin/main 1c8b320, before #20208 added this file) but the in-place-fix exemption's four criteria all hold: same defect class, mechanical fix with the form already pinned by the other four sites, the file held by no other claim (#20208 is merged), same gate family with no new verification surface. This amends the claim's declared file surface (packages/lint/src/validate-mapping-target-fields.ts and its test) in this same round, per the exemption's own requirement -- flagged here since I hold no claim of my own to amend it in. (2) The verify-lock queue was exceptionally contended this round: one sibling holder (issue-19926's tests1.sh) held the shared os-verify-lock for ~1150s straight, queuing the targeted vitest run behind it for a comparable stretch before it finally released; retried with the same OS_VERIFY_LOCK_SLOT rather than a fresh ticket each time, per the queueing contract -- named here only because the elapsed wall-clock this round is unusually high for a 4-file vitest run, not because anything was skipped. (3) mcp_calls / api_writes routing -- see open_questions; no deviation from the write BUDGET (still exactly pr_create x1, label-write x1, comment x1), only from the dispatch order's assumed identity for those writes.", "files_changed": [ "packages/lint/src/object-graph.ts", "packages/lint/src/object-graph.test.ts", "packages/lint/src/validate-object-references.ts", "packages/lint/src/validate-object-references.test.ts", "packages/lint/src/validate-translation-references.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-mapping-target-fields.ts", "packages/lint/src/validate-mapping-target-fields.test.ts", ".changeset/20206-lint-packages-non-array-refused.md" ] }objectstack-fleet commentedon Sep 27, 2026
REWORK round 1 — PR #20229 at head
bd29ec386f·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T12:38ZSeat review against GitHub, not the report:
main,Fixes #20206, 9 files, +173/−12, all inpackages/lintplus.changeset/.Clause-②: no (narrowing).packagesOf(stack)reader inobject-graph.ts, andrecordsOfis untouched.INVALID_ARTIFACT_PACKAGES(422).{},0and'x'at each validator, with the array as the control.5855240019(recorded here, ⛔ not a second claim):packages/lint/src/validate-mapping-target-fields.tsand its test, a fifth identical site that fix(rest,lint): refuse an import mapping target that names no field, on the dry run, the commit and validate alike #20208 added after the card's census. It is accepted under the in-place-fix exemption.nullleg moves here:packages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926 (5805260775) reads 「nullis malformed, everywhere」:nullbecomes a refusal at every reader.nullout of scope with a pointer topackages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926, butpackages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926's claim fencedpackages/lintout as this card's surface. The lint leg had no owner, whichpackages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926's dev found:validate-object-references.test.tspins[null, 42, 'core']as "no packages".packages: nullas malformed, never absent #20228 also corrects the spec rule text to "every reader refuses it". The seat assigns the lint leg here, as the execution of an existing ruling, not a new decision.packagesOftreats onlyundefinedas absent and refusesnullwith the same envelope. The message namesnullrather thantypeof'sobject, as PR fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228's core template does.nullis left exactly this way on purpose" sentence goes.nullpins flip at each validator, and the changeset states it, citing ruling5805260775.objectstack-fleet[bot]is the documented mechanism. The dispatch order's sentence about the ambient token was imprecise, and nothing needs fixing.Clause-②: no, and nothing touchespackages/spec/src/**.fix(lint,spec): packagesOf refuses packages: null too, and lists its …
objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 20206, "status": "done", "branch": "claude/issue-20206-lint-packages-non-array-refused", "pr": "https://github.com/objectstack-ai/objectstack/pull/20229", "session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)", "premise_still_valid": true, "summary": "REWORK round 1, per seat comment 5855890525. (1) The null leg (ruling A on #19926, 5805260775): packagesOf now treats only undefined as absent; null falls to the same INVALID_ARTIFACT_PACKAGES refusal as {}/0/'x'/a keyed object, message naming null as itself per PR #20228's template. Flipped the null pins at packagesOf and at each of the three public functions its five readers sit behind. (2) CI fix: check:error-code-provenance was red (packages/lint stamps the registered code INVALID_ARTIFACT_PACKAGES without an owner-key row) -- reproduced locally, fixed with a new '@objectstack/lint' row in packages/spec/src/api/error-code-ledger.zod.ts (door: 'none', no code minted), modelled on precedent 3f9e2eaa1c. Two changesets now: @objectstack/lint minor (Clause-②: no (narrowing), unchanged) and a new @objectstack/spec minor (Clause-②: yes) for the ledger row; the PR-level declaration is now Clause-②: yes (narrowing), carrying both facts. origin/main had moved on the same ledger file (137 files, unrelated) since round 0; merged clean, no conflicts, re-verified. New head a4b05d6e1576e3bc3cecf95851d724f4ebb4e448. PR body patched (one issue_patch, body only) with the null sentences, the CI-fix section and the new head; read back byte-identical.", "tests": "Reproduced the CI failure first: `npx tsx packages/spec/scripts/check-error-code-provenance.ts` on the prior head printed 'FAIL -- 1 stamp site(s) of a registered code with no provenance row: @objectstack/lint stamps INVALID_ARTIFACT_PACKAGES (assign) at packages/lint/src/object-graph.ts:278 -- not listed under its own owner key'. After the ledger row: same command -- OK, 312 listed (was 311), 9 waivers all live; --self-test also green. pnpm --filter @objectstack/spec build && check:generated -- green, all 15 generated artifacts up to date (run post-merge). pnpm --filter '@objectstack/lint^...' build -- green (spec DTS + formula). pnpm --filter @objectstack/lint typecheck -- green, no new debt. pnpm --filter @objectstack/spec exec vitest run src/api/error-code-ledger.test.ts -- 21/21 passed. pnpm --filter @objectstack/lint exec vitest run the five test files -- 232/232 passed, both pre- and post-merge. check:error-code-casing, check:dispatcher-error-vocabulary, check:strictness-ledger -- all green (newly derived by dispatch-gates.mjs once the diff touches the ledger file). check:adr-0087-registration -- green, changeset now reads [clause-②-narrowing] not-required (no-migration-prescription) for the lint changeset; the spec changeset (plain Clause-②: yes) carries no marker, matching the precedent. check:changeset-no-major --event (a synthetic pull_request body carrying 'Clause-②: yes (narrowing)') -- LEVEL AXIS clean, no package graded patch. check:nul-bytes, check:issue-citations, check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check:type-check-coverage, check:published-files, check:watch-hint-literal -- all green, unchanged from round 0. ABLATION (scripts/ablation-replace.mjs, WRAP form, against the real commit 4d221c0177 -- the first attempt was run before that commit existed and its restore-to-HEAD silently reverted the uncommitted null-leg fix; caught by re-reading the file, re-applied, re-verified green, then redone correctly after committing): anchor 'if (declared === undefined) return [];' -> 'if (declared === undefined || declared === null) return [];' in object-graph.ts. Mutation landed (anchor 1->0, blob 96757eb0d1ee->f9f049d4b484). All four null pins went red, one per test file (object-graph.test.ts, validate-object-references.test.ts, validate-translation-references.test.ts, validate-mapping-target-fields.test.ts), each 'expected function to throw an error, but it didn't'; 228/232 passed (exactly the 4 null assertions failed, nothing else). Restore proven: blob after restore 96757eb0d1ee == blob at HEAD 96757eb0d1ee, git diff HEAD empty, git status clean.", "mcp_calls": "0 -- no MCP GitHub tool calls this round either; reads via curl, writes via scripts/pm/ (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (this is a cloud seat container; see round 0's open_questions, answered A by the seat's own review comment).", "api_writes": "3 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 9974 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x2 (not REST writes): the rework commit, then the merge commit after origin/main moved on the touched ledger file.", "open_questions": [], "out_of_scope_findings": [], "gates": "green this round: check:error-code-provenance (reproduced red first, then green -- the CI-failing gate), check:error-code-casing, check:dispatcher-error-vocabulary, check:strictness-ledger, check:generated (all 15 spec artifacts, post-merge), @objectstack/lint typecheck, @objectstack/spec + @objectstack/lint vitest (21 + 232 tests), check:adr-0087-registration, check:changeset-no-major (plain and with a synthetic yes (narrowing) event), check:nul-bytes, check:issue-citations, check:cross-package-test-inputs, check:test-source-alias, check:doc-authoring, check:type-check-coverage, check:published-files, check:watch-hint-literal. dispatch-gates --ran reconciliation: 13 of 86 now-derived families measured locally this round (up from 9 of 60 in round 0 -- the diff now also touches packages/spec/src/api/error-code-ledger.zod.ts, which is exactly the family whose local absence let the CI failure through last round); the rest left to CI, mostly repo-wide --self-test checker-health invocations and generated-artifact sub-checks already covered wholesale by the green check:generated run.", "line_budget": "n/a", "deviations": "(1) Touched packages/spec/src/api/error-code-ledger.zod.ts -- outside the claim's originally declared file surface (packages/lint/** + .changeset/) -- under the coordinator's explicit rework instruction to fix the CI-failing gate the way it prescribes; no allowlist, no waiver, no new code minted, a plain ledger row modelled on a direct precedent (3f9e2eaa1c). (2) A self-inflicted ablation mishap, caught and corrected before it reached any write: the first ablation-replace.mjs run was launched against an UNCOMMITTED null-leg fix, so its restore-to-HEAD (HEAD still being the pre-rework commit) silently reverted that fix along with the ablation's own mutation -- exactly the hazard AGENTS.md's 'commit the fix FIRST' rule names. Caught by re-reading object-graph.ts immediately after (it showed the pre-rework code), re-applied the fix, re-verified green, committed, and reran the ablation cleanly against the real commit -- see the tests field for both readings. Nothing incorrect reached a push or a report; recorded here because the near-miss is the kind of thing the next round should not repeat. (3) origin/main moved on the same ledger file between round 0 and this round (137 files total, unrelated content -- retiring a different StandardErrorCode member); merged (no rebase, no amend, no force-push), clean, no conflicts, re-verified in full post-merge.", "files_changed": [ "packages/lint/src/object-graph.ts", "packages/lint/src/object-graph.test.ts", "packages/lint/src/validate-object-references.test.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-mapping-target-fields.test.ts", "packages/spec/src/api/error-code-ledger.zod.ts", ".changeset/20206-lint-packages-non-array-refused.md", ".changeset/20206-lint-error-code-provenance-row.md" ], "head_sha": "a4b05d6e1576e3bc3cecf95851d724f4ebb4e448" }objectstack-fleet commentedon Sep 27, 2026
ACCEPT (after REWORK round 1) — PR #20229 at head
a4b05d6e15·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T14:29ZSeat review against GitHub, not the report:
nullleg (order5855890525): delivered.packagesOftreats onlyundefinedas absent.nulltakes the sameINVALID_ARTIFACT_PACKAGES(422) refusal, with a message namingnull.nullpins flip atpackagesOfand behind each validator.nulland turned exactly the fournullpins red (228/232), then was restored clean.check:error-code-provenance(addendum): reproduced ("@objectstack/lintstampsINVALID_ARTIFACT_PACKAGES… not listed under its own owner key"), then fixed the way the gate prescribes.packages/spec/src/api/error-code-ledger.zod.tsgains an@objectstack/lintowner row (door: 'none', following precedent3f9e2eaa1c). No code is minted and no waiver is used.5855240019(recorded here, ⛔ not a second claim):packages/spec/src/api/error-code-ledger.zod.ts(the one owner row) and a second changeset (@objectstack/specminor).Clause-②: no→Clause-②: yes (narrowing).@objectstack/spec, which is the widening. The lint readers refuse a shape they accepted, which is the narrowing.Clause-②line carried its explanation on the same line.clause2-line.mjsreadClause2Lineread that as a near-miss (describing), which is no declaration at all. The seat moved the explanation to its own paragraph with oneissue_patch; the line now readsdeclared yes / narrowing, and the body was read back byte-identical.validate-mapping-target-fields.ts, and a driver-freemerge-treeof the two heads is clean.packages: nullas malformed, never absent #20228 (packages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926) is enqueued no earlier than this PR.os validate/os buildaccept a view container whoseobjectnames no object in the stack — no error, no advisory — and the runtime'sgetViewsByObjectthen never finds the view #20216 is region-serial behind this PR invalidate-object-references.ts.packages/spec/src/api/error-code-ledger.zod.ts), and so does the declaration limb.needs:contract-reviewgoes on the PR in this act.objectstack-fleet commentedon Sep 27, 2026
REWORK round 2 — PR #20229 at head
a4b05d6e15, from the at-tier record on the PR (FAIL) ·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T14:41ZBlocking, per the record's items 1 and 2, and nothing else:
validate-object-references.test.tspins only[null, 42, 'core']. Add{}and a keyed object ({ a: { manifest: {} } }), the one shaperecordsOfread as a map, so all three validators pin the card's set.validate-object-references.tsandvalidate-translation-references.ts" becomes five sites in three files, includingvalidate-mapping-target-fields.ts. This covers theobject-graph.tsdocblock and theobject-graph.test.tscomment.null"the wayresolveArtifactPackageOrderdoes", or raises the "SAME code … for the identical defect", is true only once PR fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228 (packages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926) lands: onmain, core still readsnullas absent. Qualify each (docblock, inline comment, lint changeset, ledger-row comment) with "fornull, oncepackages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926 (PR fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228) lands", or state the lint rule on its own terms.os validate/os lint/os buildsurface the refusal on stderr. In factos validateandos buildrefuse first atObjectStackDefinitionSchema.safeParseand never reach the lint reader. Onlyos lintreaches it: exit 1, the message on stdout (printError), andcodeunder--json. State exactly that.packagesOfsemantics (undefinedabsent; array as before;null,{},0,'x'and keyed objects refused with 422 and the code);recordsOfuntouched;os lint's catch-all: exit 1, the code under--json) is acceptable;Clause-②: yes (narrowing), bothminorgrades and the ADR-0087 disposition.packages: nullas malformed, never absent #20228 behind this PR. Both enter the queue in one window once this PR has its PASS: this PR first, then fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228.3 remaining items
test(lint): close the round-3 pin gap at the mapping validator
objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 20206, "status": "done", "branch": "claude/issue-20206-lint-packages-non-array-refused", "pr": "https://github.com/objectstack-ai/objectstack/pull/20229", "session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)", "premise_still_valid": true, "summary": "REWORK round 3, one bounded patch under in-seat ruling 5857515836 (at-tier record 5857513507 on the PR, FAIL, item 1 only). validate-mapping-target-fields.test.ts's non-array refusal loop pinned only [{}, 0, 'x', null] -- no keyed object, and no explicit packages: undefined control (only the array control). Added { a: { manifest: {} } } to the loop, and a dedicated packages: undefined control test beside it -- targeting full_name (a field the fixture object declares directly), not sla_tier (which only resolves through the objectExtensions a package supplies, so it cannot serve as a genuine 'packages absent' control -- caught and fixed within this same unpushed commit before it ever reached a push). All three validators now pin the same shape classes: {}, a number, a string, null and a keyed object, plus an array control and an explicit undefined control. Reworded the one place that over-claimed this before the fix landed -- the PR body's round-2 line ('all three validators now pin the identical set') and validate-object-references.test.ts's matching comment both said 'the identical/same set'; both now say 'the same shape classes', true now that the gap is closed. Nothing else moved: packagesOf semantics, the five readers, the ledger row, the door sentences and the null-parity conditioning are all unchanged since 68398a0e7e, per the ruling's own scope. Addendum handled: PR #20228 has not merged as of this push (checked via the REST API immediately before pushing), so pushed as planned per the addendum's instruction for that case, and the PR body now carries the required landing-order line naming the red CI leg in packages/cli/test/null-packages-follows-resolver.test.ts and that it clears once #20228 lands; packages/cli and that test file were not touched. New head 42b3bfbf2ee9beb4c806b051c2e9597009695e01. PR body patched (one issue_patch, body only) with the round-3 corrections, the round-3 gates subsection and the addendum line; read back byte-identical (19584 bytes).", "tests": "Re-created the worktree fresh (prior one had been cleaned up after round 2) at the coordinator-confirmed head 68398a0e7e, pnpm install, implemented both edits, then re-checked origin/main (fetched fresh) against the two edited files -- untouched, no merge needed -- before committing. check-adr-0087-registration.mjs --base origin/main and check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying the real, final PR body byte-for-byte) -- both green against the final body. Under scripts/pm/os-verify-lock.sh, stable slot issue-20206-dev-r3 (lock free both times this round, no queueing): pnpm --filter '@objectstack/lint^...' build -- green. pnpm --filter @objectstack/lint typecheck -- green, same pre-existing debt as prior rounds (2 files / 6 errors / 2 pinned signatures, unrelated, shrink-only). pnpm --filter @objectstack/lint exec vitest run the four packagesOf-reaching test files -- FIRST PASS caught a self-inflicted bug: the new CONTROL test targeted mapping target 'sla_tier', which only resolves via the objectExtensions a package supplies (the array-control test two cases above it provides that extension) -- with packages: undefined (genuinely absent) that target produced a real MAPPING_TARGET_FIELD_UNKNOWN finding instead of staying silent, so the control failed its own toEqual([]) assertion (1 failed, 232 passed, 233 total). Fixed by retargeting the control at 'full_name' (a field the fixture object `contact` declares directly, matching the style of the existing 'is green on the control mapping' test) and amending the still-unpushed commit (no force-push; nothing had reached the remote yet). Re-ran clean: 233/233 passed. ABLATION (scripts/ablation-replace.mjs, WRAP form, against the real committed head 42b3bfbf2e): first attempt used anchor 'if (Array.isArray(declared)) return declared.filter(isRec);' with a replacement that re-included that exact substring as a prefix -- the tool's own anchor-drop invariant refused it before running anything ('the anchor count moved 1 -> 1, a drop of 0, not the declared 1'; ABLATION-SCRIPT-EXIT=1, no test ever ran, tree auto-restored -- caught and corrected, nothing incorrect reached a report). Re-anchored on the same line with a replacement that folds the condition into one combined guard (Array.isArray(declared) || isRec(declared)) so the literal anchor text does not recur -- mutation landed cleanly (anchor 1->0, blob 3452a2830d0d->28efa28d64ab). Mimicking packagesOf's old recordsOf-style object handling is the ONLY available anchor for 'the keyed-object refusal': {} and a keyed object share the exact same guard in the implementation, so ablating one necessarily ablates both -- there is no narrower branch. 5 tests went red: object-graph.test.ts's {} and keyed it.each cases explicitly (both named individually), plus all three validators' refusal loops, each of which stops at its first affected element -- {} is first in the mapping and translation loops, so the new keyed assertion at the end of the mapping loop is covered by that same failing test rather than isolated on its own; object-refs' loop is ordered [null, 42, 'core', {}, keyed] so it also stops at {}. 228/233 passed overall; 0 / 'x' / null stayed refused throughout, confirming the anchor is object-shape-specific and does not touch the primitive/null refusal paths verified in rounds 1-2. Restore proven: blob after restore 3452a2830d0d == blob at HEAD 3452a2830d0d, `git diff HEAD` empty, `git status` clean.", "mcp_calls": "0 -- no MCP GitHub tool calls this round; reads via curl, writes via scripts/pm (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (cloud seat container, per round 0's answered open question).", "api_writes": "2 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 19584 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x1 (not a REST write): the round-3 commit, amended once pre-push (unpublished) to fix the CONTROL test's mapping-target bug, then pushed once.", "open_questions": [], "out_of_scope_findings": [], "gates": "green this round: check-adr-0087-registration, check-changeset-no-major (both against the real final PR body), @objectstack/lint build+typecheck, @objectstack/lint vitest (233 tests, 4 files), check:commit-card-trailers (model-free trailers carried through the amend). Nothing else re-derived or re-run: the diff touches only two lint test files (no ledger, no changeset, no error-code stamp site), so dispatch-gates' family list for this round is a strict subset of round 2's already-measured 86; not independently re-derived since the coordinator's own scope statement ('Nothing else moves') and the ruling's boundary flags already establish the rest of the head as correct. CI will re-run the full farm on the new head regardless, including the packages/cli leg the addendum named, which stays red until PR #20228 lands (expected, not a defect of this PR).", "line_budget": "n/a", "deviations": "(1) Self-caught test bug: the first version of the new packages: undefined control in validate-mapping-target-fields.test.ts targeted a mapping field (sla_tier) that only resolves through package-supplied objectExtensions, so it produced a real finding instead of staying silent and failed its own assertion. Caught by the FIRST test run (not by any external review), fixed by retargeting at full_name, amended into the still-unpushed commit before anything reached the remote. (2) Ablation tool refusal, corrected before any write: the first ablation-replace.mjs invocation chose an anchor/replacement pair where the replacement text still contained the literal anchor substring as a prefix, so the tool's own anchor-drop check refused to run the wrapped test command at all (mutation was written then immediately auto-restored, exit 1, nothing measured). Re-anchored with a combined-condition replacement that does not reproduce the anchor text, which the tool accepted and ran cleanly. (3) The keyed-object ablation is necessarily broader than 'keyed only': packagesOf has no code path that distinguishes an empty object from a keyed one -- both are isRec, both hit the identical guard -- so the ablation's 5 red tests include the pre-existing {} pins at every validator alongside the new keyed pin at the mapping validator's loop (which is covered by the same failing test, not isolated as its own red line, since the loop stops at its first failing element). Reported exactly as observed rather than claimed as a narrower result. (4) Addendum: CI shows packages/cli/test/null-packages-follows-resolver.test.ts leg 1 red on this PR's line of heads because it requires every reader (including core's own resolveArtifactPackageOrder) to treat packages: null identically, and core still reads null as absent until PR #20228 (#19926) lands and is merged ahead of this PR in the queue. Confirmed #20228 is still open/unmerged via the REST API immediately before this push; per the addendum's own branching instruction for that case, pushed as planned without touching packages/cli or that test, and added the required one-line landing-order note to the PR body.", "files_changed": [ "packages/lint/src/object-graph.ts", "packages/lint/src/object-graph.test.ts", "packages/lint/src/validate-object-references.test.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-mapping-target-fields.test.ts", "packages/spec/src/api/error-code-ledger.zod.ts", ".changeset/20206-lint-packages-non-array-refused.md", ".changeset/20206-lint-error-code-provenance-row.md" ], "head_sha": "42b3bfbf2ee9beb4c806b051c2e9597009695e01" }objectstack-fleet commentedon Sep 27, 2026
In-seat ruling
5857515836, extended — PR #20229 at head42b3bfbf2e·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T16:43Zvalidate-mapping-target-fields.test.ts:107-108), and two PR body phrases, saysla_tierresolves via a package-suppliedobjectExtensions. In that fixture it is the STACK's ownobjectExtensions, andregionis the package-supplied one.packagesOfand the ledger are "untouched sincea4b05d6e15". Their comments moved in68398a0e7e; only the function body and the row entry did not.origin/mainhad not touched the edited test file. PR feat(spec,rest,lint): an import mapping target may name a declared part of a compound field (mailing_address.street) #20246 (443b2f4fdc) had, and the merge is clean.packages: nullas malformed, never absent #20228's landing (a9fb83ef06). A fresh at-tier record then judges that one head: the merge delta, these sentences, andnull-packages-follows-resolver.test.tsleg 1 green.test(lint): correct which objectExtensions source the round-3 comment…
objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 20206, "status": "done", "branch": "claude/issue-20206-lint-packages-non-array-refused", "pr": "https://github.com/objectstack-ai/objectstack/pull/20229", "session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)", "premise_still_valid": true, "summary": "Merge-only round: PR #20228 (#19926, core refuses packages: null) merged as a9fb83ef06 at 16:40Z. Recreated the worktree at the coordinator-confirmed head 42b3bfbf2e, fetched and merged origin/main (3fef33e23b, no rebase, no force-push) -- clean, no conflicts. Diff old head -> merge commit: 287 files, 8926 insertions, 1939 deletions, split exactly into from-main (all 287 files, verified by set-equality against origin/main's own diff from the pre-merge merge-base) and anything else (empty by construction -- no manual conflict resolution, git status was clean immediately after the merge). A mid-round addendum reported a fresh at-tier FAIL on the pre-merge head (42b3bfbf2e) over four text-only issues; folded all four into the same push as their own commit (82dc0c9c01, model-free trailers): (1) the round-3 CONTROL test's comment mis-attributed sla_tier to a package-supplied objectExtensions -- it is the STACK's own; region is the package-supplied one -- reworded in the test file and mirrored in the PR body's round-3 bullets; (2) the PR body's 'packagesOf and the ledger untouched since a4b05d6e15' corrected to 'the function body and the row entry unchanged; their comments moved in round 2'; (3) the PR body's stale 'origin/main has not touched either edited test file, no merge needed' corrected to name PR #20246 (443b2f4fdc), which did touch validate-mapping-target-fields.ts/test.ts, and point at this round's clean merge. New head 82dc0c9c012ce3eb1ff24a4bb9a2675fad594a4c. PR body patched (one issue_patch, body only) with the round-4 merge section, the four addendum corrections, and the landing-order line naming a9fb83ef06 and the new head; read back byte-identical (23685 bytes). Clause-②: yes (narrowing) kept alone on its own line throughout.", "tests": "origin/main re-checked before the merge and again immediately before the push (a third origin/main commit, 17bd318771, landed in between -- unrelated spec types, touches none of this PR's files, not re-merged since nothing to pick up). check-adr-0087-registration.mjs --base origin/main and check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying the real, final PR body byte-for-byte) -- both green post-merge. Under scripts/pm/os-verify-lock.sh, stable slot issue-20206-dev-r4 (severe contention -- the @objectstack/cli^... dependency closure build needed six attempts before it landed: four queue-timeout (exit 99, NOT MEASURED, place kept each time), one killed by this session's own 590s foreground wrapper at 31/38 then 54/55 tasks already turbo-cached from the partial run before it -- caching made each retry faster, and the sixth attempt finished clean in 10s against the warm cache): pnpm exec turbo run build --filter='@objectstack/lint^...' AND --filter='@objectstack/cli^...' -- both green, 55/55 tasks on the cli closure. pnpm --filter @objectstack/cli exec vitest run test/null-packages-follows-resolver.test.ts -- 16/16 passed, both legs (leg 1: 1 resolver-verdict test + 7 READERS cases = 8; leg 2: 7 READERS cases + 1 absent-key control = 8). Leg 1's named case '`os lint` lintConfig' (READERS[3], packages/cli/test/null-packages-follows-resolver.test.ts:107, confirmed by reading the READERS array directly) is GREEN, now that resolveArtifactPackageOrder genuinely refuses null post-#20228, matching lintConfig's own refusal path -- part of the 16/16, not re-isolated with a separate verbose run (a follow-up verbose re-run was attempted for a by-name transcript but was cut short by this session's own 120s wrapper before it reached the lock; the source-read of READERS[3] plus the aggregate 16/16 from the prior clean run is the evidence, and no further lock cycle was spent chasing a purely cosmetic transcript). pnpm --filter @objectstack/lint typecheck -- green, no new debt. pnpm --filter @objectstack/lint exec vitest run the four packagesOf-reaching test files -- 237/237 passed (up from 233: PR #20246 added 4 tests to validate-mapping-target-fields.test.ts for its own compound-field-mapping feature; none touch packages). check:commit-card-trailers -- green on both the merge commit and the wording-fix commit.", "mcp_calls": "0 -- no MCP GitHub tool calls this round; reads via curl, writes via scripts/pm (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (cloud seat container, per round 0's answered open question).", "api_writes": "2 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 23685 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x1 (not a REST write): two commits in one push -- the merge commit (3fef33e23b) and the wording-fix commit (82dc0c9c01) -- both with model-free trailers, no force-push.", "open_questions": [], "out_of_scope_findings": [], "gates": "green this round: check-adr-0087-registration, check-changeset-no-major (both against the real final PR body, post-merge), @objectstack/lint^... build, @objectstack/cli^... build (55/55), @objectstack/lint typecheck, @objectstack/lint vitest (237 tests, 4 files), @objectstack/cli vitest (null-packages-follows-resolver.test.ts, 16/16, both legs), check:commit-card-trailers. Not independently re-derived: dispatch-gates --commands for the merge's own 287-file diff, since that diff is defined to be exactly origin/main's own commits (verified by the file-set-equality check reported above) and CI has already graded every one of those commits on main; the addendum's four text-only corrections touch one test file's comment and PR-body prose only, no code, ledger, or changeset content, so no new gate family is implicated beyond what rounds 1-3 already measured.", "line_budget": "n/a", "deviations": "(1) The @objectstack/cli^... build's dependency closure is effectively most of the monorepo (CLI depends on ~50 workspace packages); it took six attempts under the shared lock across roughly 30-40 minutes of cumulative queueing and building before landing clean, driven by severe multi-agent contention (holders included sibling agents' own spec/cli/plugin-security builds and test runs) plus turbo's own build time once acquired. Retried throughout with the same stable slot per contract; each retry's turbo cache made the next attempt faster (31/38 -> 54/55 -> 55/55 cached). Recorded as the round's dominant cost, not a defect. (2) A follow-up vitest run requesting a verbose, by-name transcript of the CLI lockstep test was cut short by this session's own 120s wrapper (too short for the lock's queue, not the lock's fault) before reaching the lock; rather than spend another lock cycle on a purely cosmetic re-run, the report cites the aggregate 16/16 pass (already proven in the prior clean run) plus a direct source-read of the READERS array naming the 'os lint lintConfig' case at line 107 -- the same evidentiary standard the coordinator's own addendum used to name the failing leg before the fix. (3) A mid-round addendum (a fresh at-tier FAIL on the pre-merge head 42b3bfbf2e, text-only) was folded into this same push as instructed: see summary items (1)-(3) there. Nothing in that addendum touched code, and none of it was self-discovered -- it arrived from the coordinator mid-round, addressed in full before this report.", "files_changed": [ "packages/lint/src/object-graph.ts", "packages/lint/src/object-graph.test.ts", "packages/lint/src/validate-object-references.test.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-mapping-target-fields.test.ts", "packages/spec/src/api/error-code-ledger.zod.ts", ".changeset/20206-lint-packages-non-array-refused.md", ".changeset/20206-lint-error-code-provenance-row.md" ], "head_sha": "82dc0c9c012ce3eb1ff24a4bb9a2675fad594a4c" }objectstack-fleet commentedon Sep 27, 2026
In-seat ruling
5857515836, extended again — PR #20229 at head82dc0c9c01·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T18:04Z3fef33e23bis pure (tree-identical to a driver-free merge of its parents);packagesOf;null-packages-follows-resolverleg 1 is green from source with fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228 in the head;packages: nullas malformed, never absent #20228 (a9fb83ef06) in this head, five present-tense sentences saying core "still readsnullas absent" and that the readers align "once fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228 lands" are false: threeobject-graph.tscomments, the lint changeset and the ledger-row comment.docs(lint,spec): null-parity sentences state present tense, now that #…
objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 20206, "status": "done", "branch": "claude/issue-20206-lint-packages-non-array-refused", "pr": "https://github.com/objectstack-ai/objectstack/pull/20229", "session": "session_01QcAS3qiYYZNezaxZxaUdMV — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)", "premise_still_valid": true, "summary": "One more wording round, seat ruling 5857515836 extended on #20206: the at-tier record on head 82dc0c9c01 FAILed only because round 4's merge brought PR #20228 (a9fb83ef06) in, making every 'once #20228 lands' sentence false. Six exact wording edits applied, no code changed: (1)-(3) packages/lint/src/object-graph.ts's packagesOf docblock (the null bullet, the @throws block) and its inline naming comment -- each restated in the present tense (null refused exactly as resolveArtifactPackageOrder refuses it, same code/status, since #19926 PR #20228 a9fb83ef06; core raises the same code for null too; the same naming resolveArtifactPackageOrder uses). (4) the lint changeset's parity sentence. (5) the ledger row comment in error-code-ledger.zod.ts. (6) the PR body's round-3 gates sentence, replaced with the coordinator's exact text naming PR #20246 (443b2f4fdc, verified via the GitHub API at 2026-09-27T15:58:56Z) and round 4's clean merge; round-1/2 history sections quoting the old 'has not landed' wording were left untouched, as instructed (they are historical record under the Landing-order paragraph). Ran the required git grep sweep (once (it|#20228|#19926|PR #20228)[^.]*lands|still reads (it|`null`) as absent|adopted early) over this PR's own changed files -- 0 hits; the same sweep over the full PR body shows hits only inside the exempt round-1/2 history sections. packages/cli/src/utils/stack-collections.ts:174 left untouched, as instructed. Committed as one commit (a38a259df6, model-free trailers), no rebase, no amend of a published commit, no force-push. New head a38a259df60e106370f2ea9e9060e12bab8f59b6. PR body patched (one issue_patch, body only); read back byte-identical (23565 bytes). Clause-②: yes (narrowing) kept alone on its own line.", "tests": "origin/main re-checked twice (once before the commit, once immediately before the push) via merge-base against origin/main -- both times the merge-base stayed at a9fb83ef06 (PR #20228's own merge commit) and the only new origin/main content since then was three unrelated changesets; no merge needed. check-adr-0087-registration.mjs --base origin/main -- green, pre- and post-commit. Under scripts/pm/os-verify-lock.sh, stable slot issue-20206-dev-r5 (lock acquired on the first attempt this round, no queue-timeouts): pnpm --filter '@objectstack/lint^...' build -- green. pnpm --filter @objectstack/lint typecheck -- green, no new debt. pnpm --filter @objectstack/lint exec vitest run the four packagesOf-reaching test files -- 237/237 passed, UNCHANGED from before this round's edits (comment- and changeset-only, no test-visible behavior moved, as predicted). pnpm --filter @objectstack/spec run check:error-code-provenance -- green: self-test OK, then 'scanned 2482 files; 328 registered-code stamp site(s): 312 listed, 16 waived ... every registered-code stamp site is listed under its own owner key or carries a recorded waiver (9 waiver(s), all live)'. check-changeset-no-major.mjs --base origin/main --event (a synthetic pull_request payload carrying the real, final PR body byte-for-byte) -- green, re-run against the body actually posted: 'LEVEL AXIS: this PR declares clause-② yes (narrowing)'. check:commit-card-trailers -- green on the push.", "mcp_calls": "0 -- no MCP GitHub tool calls this round; reads via curl, writes via scripts/pm (with-fleet.sh --via dispatch for the PR body patch, post-stamped.mjs for this comment) through the fleet-write relay as objectstack-fleet[bot] (cloud seat container, per round 0's answered open question).", "api_writes": "2 -- PATCH /repos/objectstack-ai/objectstack/issues/20229 (issue_patch, body only, via the relay -- read back byte-identical, 23565 bytes); POST /repos/objectstack-ai/objectstack/issues/20206/comments (this report, post-stamped.mjs). git push x1 (not a REST write): one commit (a38a259df6), model-free trailers, no force-push.", "open_questions": [], "out_of_scope_findings": [], "gates": "green this round: check-adr-0087-registration, check-changeset-no-major (both against the real final PR body), @objectstack/lint build+typecheck, @objectstack/lint vitest (237 tests, 4 files, unchanged), check:error-code-provenance, check:commit-card-trailers. Not re-run: the @objectstack/cli dependency closure and null-packages-follows-resolver.test.ts (round 4 already proved these against the identical code state this round's edits don't touch -- comments and the changeset/ledger prose only; the coordinator's own proof list for this round names only the four lint test files, check-adr-0087-registration, check-changeset-no-major and check-error-code-provenance, all of which ran green).", "line_budget": "n/a", "deviations": "None this round. All six edits matched the coordinator's exact text (adapted only where a sentence's surrounding grammar required a small connective change, e.g. splicing 'core raises this code for null too (#19926)' into its sentence); the required grep sweep returned 0 hits on the first run; no build, test, or gate failure was hit and no retry was needed.", "files_changed": [ "packages/lint/src/object-graph.ts", "packages/lint/src/object-graph.test.ts", "packages/lint/src/validate-object-references.test.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-mapping-target-fields.test.ts", "packages/spec/src/api/error-code-ledger.zod.ts", ".changeset/20206-lint-packages-non-array-refused.md", ".changeset/20206-lint-error-code-provenance-row.md" ], "head_sha": "a38a259df60e106370f2ea9e9060e12bab8f59b6" }objectstack-fleet commentedon Sep 27, 2026
Landing record — PR #20229 merged through the merge queue at 2026-09-27T18:57Z as
5f9d7d7864·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T18:58Zorigin/main:5f9d7d7864is an ancestor.packages/lint/src/object-graph.ts:259-270refuses a present non-arraypackagesother thannull, withstatus: 422.nullfalls to the same refusal, per the resolver ruling onpackages: nullon a release artifact: the schema andcomposeStacksrefuse it, while every reader reads it as absent #19926. It returnsdeclared.filter(isRec)only for an array.validate-*readers route through it.@objectstack/lintowner row is inpackages/spec/src/api/error-code-ledger.zod.ts.completedbyFixes #20206.pm:dispatchedis stripped in this act;bug,domain:spec,priority:p3andarea:devpathstay.5855890525, then ACCEPT5856727079.5856823202, then REWORK round 25856828928.5857513507, then ESCALATE, ruled in-seat (5857515836, a verification-strategy class), then extended twice (5857781425,5858385827) across two more at-tier FAILs (5857777650,5858382308).5858580877ata38a259df6was on the PR body only. The seat corrected the body, and the re-judge on the same head is PASS 107/107 (5858613979).packages: nullas malformed, never absent #20228 (a9fb83ef06, per5857600574), sonull-packages-follows-resolver.test.tsleg 1 answered with the resolver. PR fix(lint)!: refuse a view container whoseobjectnames no object #20253 landed in the same queue group, behind it.Clause-②line readsyes (narrowing). That is the body's final line: the ledger row widens@objectstack/specand the lint readers narrow.#20206only. Nothing else closed in the window except [finding]os validate/os buildaccept a view container whoseobjectnames no object in the stack — no error, no advisory — and the runtime'sgetViewsByObjectthen never finds the view #20216, by its own PR.pm:blockedcard carriesBlocked-by: #20206.5805260775: every reader leg has now landed: core PR fix(core,runtime,plugin-dev,plugin-security): refusepackages: nullas malformed, never absent #20228, cli PR fix(cli): refuse a present non-arraypackagesin the stack-collection and docs readers #20231 (cli: the stack-collection and docs readers return nothing for a non-arraypackageswhile refusing a malformed entry, which leaves the cli outside ruling #15293-A #19925), and lint (this PR).fix(cli): refuse a present non-array `packages` in the stack-collecti…
fix(core,runtime,plugin-dev,plugin-security): refuse `packages: null`…
fix(lint): refuse a present non-array `packages` at all five stack.pa…
fix(spec): the pending connector-sync note matches the executor, and …