Skip to content

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

Closed

Description

@objectstack-fleet

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/main 1c8b320)

  • 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

  1. 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.
  2. 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.
  3. Pin: packages: {}, 0 and 'x' are refused at each validator. A well-formed array is the control. packages: null stays out of scope (packages: null on a release artifact: the schema and composeStacks refuse it, while every reader reads it as absent #19926).

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.

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
      ]
    }
  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK round 1 — PR #20229 at head bd29ec386f · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T12:38Z

    Seat review against GitHub, not the report:

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
    }
  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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:

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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:

    1. 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.
    2. Text that is false at the head:
  7. 3 remaining items

  8. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
    }
  9. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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.
      • The body says origin/main had 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.
    • Still the no-escalation class (纯文档措辞更正 / 说明书脱节), so the ruling above extends to these sentences.
    • When: they ride the merge-only push already under way for PR fix(core,runtime,plugin-dev,plugin-security): refuse packages: null as malformed, never absent #20228's landing (a9fb83ef06). A fresh at-tier record then judges that one head: the merge delta, these sentences, and null-packages-follows-resolver.test.ts leg 1 green.
  10. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
    }
  11. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    In-seat ruling 5857515836, extended again — PR #20229 at head 82dc0c9c01 · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T18:04Z

  12. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
    }
  13. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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

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

Metadata

Metadata

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:specpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions