Repository navigation
Description
Filed unassigned by the domain:engine seat (session session_01VK8rFDtg8eREaxBGX99Csn) as a cross-lane follow-up: the work lands in packages/cli, which is not this seat's lane, so it goes to the queue rather than into the originating PR. ⛔ No domain:* set — this seat does not produce that label; triage to route (expected domain:cli).
Provenance
Raised by the #11535 dev in its report and deliberately not added, because packages/cli was outside that dispatch's declared file surface. Flagged for the PM to dispose of rather than silently skipped; this card is that disposal. Landed alongside PR #11720 (Part of #11535).
One-line executable criterion
A drift entry whose category is needs_confirm (specifically the new manual_column_type_change op) must not refuse an artifact-pinned boot — asserted by feeding a real entry through runArtifactBootMigrationGate itself, not by asserting the category string.
Why this pin is worth having
#11535's detection half rests entirely on one measured fact, and today that fact is pinned only structurally (the emitted category is asserted, with the measurement written into a comment). The fact is:
severityis read by no boot gate — the ordinary boot path warns on every entry regardless of it.category === 'destructive'is read, byrunArtifactBootMigrationGate(packages/cli/src/utils/artifact-boot-migration.ts:137) atkernel:ready, before the socket opens, and it refuses the boot.- Measured through the real gate function:
destructive→ok=false;needs_confirm→ok=true;safe→ok=true.
That asymmetry is why #11535 could ship a loud finding without bricking the boot of every already-running deployment it exists to help — those deployments are, by the card's own account, all currently serving with the stale column. If a later change moved this op's category to destructive, or made the gate read severity as well, every affected deployment would stop booting, and nothing in packages/drivers/driver-sql can see that happen.
A pin in driver-sql cannot cover it: the gate lives in packages/cli and the driver package must not acquire a dependency on it.
Suggested shape (not prescriptive)
Next to packages/cli/src/utils/artifact-boot-migration.test.ts, feed a manual_column_type_change entry — category needs_confirm, severity error — through runArtifactBootMigrationGate and assert ok === true. destructive control that asserts ok === false, so the test proves the gate can still refuse; without that control the assertion passes for a gate that has stopped refusing anything, which is the false green this whole family keeps producing.
Related
- driver-sql: single→multi-value field change never migrates the existing column type (varchar/text kept), arrays silently stored as stringified literals — data corruption on Postgres #11535 — the card this follows (its auto-migration half is a live maintainer decision, [Decision] 老客户把「选一个人」的字段改成「选多个人」时,平台该自己动客户的库,还是只报警要人来动?(#11535 的自动迁移半边) #11700)
- [finding] The drift category vocabulary has no "report only" state, so a finding the platform will never reconcile must borrow
needs_confirmand be declined by the reconciler #11721 — the drift category vocabulary has no report-only state, so this op borrowsneeds_confirmand is declined by the reconciler packages/drivers/driver-sql/src/schema-drift.ts— where the op and its category are emitted
Activity
os-warren commentedon Aug 26, 2026
Claimed and dispatched
session_01W6HFzyH98W1YaQXhJUJt6o(seat post [PM seat] domain:server · seat 1 — 🟢 sunhaolin #6367)claude/issue-11728-artifact-boot-gate-pinThis claim comment is going up in the same action as the assign, on this card. Spelling that out because I broke it earlier today on #11991 — assigned the card and sent its claim comment to a different issue, leaving it assigned with no evidence of which session held it for the whole dispatch. The rule only works if its failures are visible.
What is being dispatched, and the one thing that decides whether it is worth anything
The criterion is executable and the card states it precisely: a drift entry whose
categoryisneeds_confirm— themanual_column_type_changeop — must not refuse an artifact-pinned boot, asserted by feeding a real entry throughrunArtifactBootMigrationGateitself, never by asserting the category string.⭐ The card also names the trap, and the brief carries it as a hard requirement rather than a suggestion: pair it with a
destructivecontrol assertingok === false. Without that control the assertion passes for a gate that has stopped refusing anything — which is precisely the false green this family keeps producing. A one-armed pin here would be worse than no pin, because it would look like coverage.Why the pin has to live in
packages/cliThe asymmetry it protects is real and load-bearing:
severityis read by no boot gate, whilecategory === 'destructive'is read byrunArtifactBootMigrationGateatkernel:ready, before the socket opens, and refuses the boot. If a later change moved this op's category todestructive, or taught the gate to readseveritytoo, every affected deployment would stop booting — and those deployments are, by #11535's own account, exactly the ones currently serving with the stale column. Nothing inpackages/drivers/driver-sqlcan observe that happen, and the driver package must not acquire a dependency on the CLI to try.The dev is told to re-locate the gate by symbol on the current ref rather than trusting the card's
artifact-boot-migration.ts:137citation — the card is from 2026-08-24 and line numbers have moved under three separate landings today alone.Serial check: lands in
packages/cli/src/utils/. Neither card in flight shares it — #12166 is inpackages/objectql, #12313 inpackages/platform-objects/src/identity/.Generated by Claude Code
os-warren commentedon Aug 26, 2026
{ "issue": 11728, "branch": "claude/issue-11728-artifact-boot-gate-pin", "pr": "https://github.com/objectstack-ai/objectstack/pull/12598", "status": "done", "premise_still_valid": true, "clause2": "no", "clause2_evidence": "git diff --stat vs merge-base b72db0198 (three-dot): '1 file changed, 190 insertions(+)' — 0 deletions, and the single path is packages/cli/src/utils/artifact-boot-migration.report-only-drift.test.ts, a .test.ts. No production file is touched, so there is no surface on which accept/reject behaviour could move. Confirmed on the final commit 0f99e0693b with a clean `git status`. No existing assertion was changed: the sibling suite artifact-boot-migration.test.ts is untouched (0 deletions).", "gateTableReverified": "yes — re-derived by symbol on current origin/main, not from the card's line citation. `runArtifactBootMigrationGate` is at packages/cli/src/utils/artifact-boot-migration.ts:109 and the category read at :137 (`drift.filter((d) => d.category === 'destructive')`); the card's :137 citation happens to still land on it, but was re-located rather than trusted. All three rows reproduce, measured through the real gate function: destructive -> ok=false; needs_confirm -> ok=true; safe -> ok=true. The severity claim also holds: `grep -n severity` exits 1 (NO matches) in BOTH artifact-boot-migration.ts and commands/serve.ts, with the exit captured before any pipe. Stronger than the card's table in one respect: both non-safe rows are now REAL emitter output from diffManagedTable rather than hand-stamped entries, and the severities run OPPOSITE to the boot consequence (the entry that boots is severity 'error'; the entry that refuses is severity 'warning').", "summary": "Adds packages/cli/src/utils/artifact-boot-migration.report-only-drift.test.ts: three cases that drive REAL drift entries (built by @objectstack/driver-sql's diffManagedTable, not hand-stamped) through runArtifactBootMigrationGate. (1) the #11535 stale-column entry — op manual_column_type_change, category needs_confirm, severity error — does NOT refuse an artifact-pinned boot; (2) the required CONTROL: a real destructive entry (orphaned column, op drop_column, severity warning) still refuses, so the pin cannot pass against a gate that has stopped refusing anything; (3) with both present the refusal names the orphan alone and the stale entry still travels to the driver as ordinary non-destructive work. Using the real emitter is what makes this more than a restatement of the category string: the day schema-drift.ts emits this op as destructive, the pin goes red. No production change was needed — the gate's contract is observable from outside, so clause 2 stays 'no'. The pin lives in packages/cli because the gate does; the enabling edge already runs cli -> driver-sql (a CLI test already imports that drift surface at run time), so no package edge and no KNOWN_UNALIASED_TEST_IMPORTS entry was added.", "tests": "All figures from final commit 0f99e0693b (clean tree); gate union derived at that same commit via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` over the real changeset (1 path, three-dot vs merge base b72db0198). Every exit captured with NO pipe between command and $?; each gate quoted by its OWN verdict line, never a bare $?. BASELINE: `pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2` over both artifact-boot-migration suites -> 'Test Files 2 passed (2) / Tests 14 passed (14)', VITEST_EXIT=0, wrapper 'VERDICT command-exit 0'. TYPECHECK: `pnpm --filter @objectstack/cli typecheck` EXIT=0 with the script echoed ('> @objectstack/cli@17.2.0 typecheck' / '> tsc --noEmit') — checked against the zero-match trap, it really ran. Selected gate verdict lines: check-test-source-alias 'OK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/'; check-engine-double-contract 'OK — 689 pinned, 134 in the DEBT ledger, 3 exempt'; cross-package-test-inputs 'OK: 20 package(s) read outside themselves, all declared'; where-matcher and query-options-erasure both 'baseline key set verified against b72db01: no files added'; check-type-check-coverage --re-measure 'OK — 31 ledger entr(ies) re-measured in 240.2s, 1687 raw tsc error(s) total, none above its recorded number'; check-nul-bytes 'OK (scanned 6948 text file(s) ... no raw ASCII control bytes)'; check-i18n-bundles 'OK (9 package(s) — all bundles in sync)'; check-i18n-coverage 'OK (12 config(s), 602 baselined untranslated string(s), none new)'. All heavy runs went through scripts/pm/os-verify-lock.sh (waits up to 7m33s were observed and spent on lock-free work, never on an idle round).", "gates": { "green": [ "pnpm --filter @objectstack/cli typecheck (exit 0, script echoed)", "affected suites: artifact-boot-migration.test.ts + artifact-boot-migration.report-only-drift.test.ts (14/14)", "check:cross-package-test-inputs", "node scripts/check-cross-package-test-inputs.mjs (ci.yml spelling)", "check:i18n", "check:i18n-coverage", "check:objectql-double-limit", "check:page-declaration-shape", "check:published-files", "check:slot-lookup", "check:test-source-alias", "check:type-source-resolution", "node scripts/check-ci-filter-parity.mjs", "node scripts/check-comment-mask-adoption.mjs", "node scripts/check-plugin-teardown-shape.mjs", "node scripts/docs-audit/check-affected-docs.mjs", "node scripts/docs-audit/check-drift-comment.mjs", "check:query-options-erasure (convention: adds a test file)", "check:type-check-coverage (convention)", "check:type-check-debt --re-measure (convention; needed the workspace closure built)", "check:engine-double-contract (convention)", "check:where-matcher (convention)", "check:nul-bytes" ], "red": [], "notMeasured": [ "The FULL @objectstack/cli package suite (185 files, ~13 min per its own vitest.config.ts measurements) — DECLARED NARROWING, not an omission: the two affected files were run in full and CI runs the package suite anyway. Nothing else in the union was left unmeasured.", "Recorded because the distinction matters: check:i18n and check:i18n-coverage FIRST returned exit 1 as 'PREREQUISITE NOT MET' / 'COULD NOT MEASURE' on this fresh worktree (unbuilt CLI, then unbuilt connector-mcp/connector-openapi). That is NOT MEASURED, not red — both gates say so in their own text ('Nothing was checked'). Both were then re-measured GREEN after building, and are reported under green as measured results." ] }, "ablations": [ { "name": "A — the #11728 risk realised: the gate starts refusing needs_confirm", "mutation": "packages/cli/src/utils/artifact-boot-migration.ts:137 `d.category === 'destructive'` -> `d.category !== 'safe'`", "predicted": "RED, exactly 3 failures / 14: the existing 'applies needs_confirm alongside safe', plus my new pin case, plus my new combined case. The destructive CONTROL predicted to stay GREEN (insensitive to this mutation by design).", "observed": "EXACT MATCH — 'Tests 3 failed | 11 passed (14)', VITEST_EXIT=1, and precisely the three predicted test names. Control stayed green as predicted.", "mutationProvenOnDisk": "Anchored grep -cF counts read BEFORE any result: original 1->0, mutation 0->1; plus `git diff --stat` (1 insertion, 1 deletion) and the full hunk. The mutator REFUSES to write unless the anchor matches exactly once (exit 3), so a zero-hit no-op cannot masquerade as a run.", "rebuild": "None needed, justified by IMPORT FORM not assertion: both suites reach the gate as './artifact-boot-migration.js', a RELATIVE specifier inside the same package, which vite resolves to the .ts SOURCE — no exports field, no dist consulted. (The new suite's BARE '@objectstack/driver-sql' import does resolve through exports to dist and was built beforehand; it is not mutated by either ablation.) Both ablations predicted RED, so a mutation failing to reach the running code would surface as all-green rather than hide.", "restore": "trap '<restore>' EXIT INT TERM; restore is `git checkout <branch> -- <gate path>` (never `git checkout HEAD -- .`), verified with RESTORE_DIFF_LINES=0, original line present 1, mutation line present 0." }, { "name": "B — the gate stops refusing ANYTHING (the false green the control exists for)", "mutation": "packages/cli/src/utils/artifact-boot-migration.ts:137 `d.category === 'destructive'` -> `d.category === ('nonexistent_category' as any)` (a stale read of a renamed vocabulary)", "predicted": "RED, exactly 4 failures / 14: the two existing destructive cases, my CONTROL, and my combined case — and CRUCIALLY my pin case predicted to stay GREEN. Also predicted green: 'never applies a destructive change on its own authority' (the destructive entry is excluded from the safe half too by a separate unmutated filter, so the driver is never called and .every() over an empty array is vacuously true).", "observed": "EXACT MATCH — 'Tests 4 failed | 10 passed (14)', VITEST_EXIT=1, precisely the four predicted names, and the pin case is absent from the failure list, i.e. it passed. The vacuous-.every() prediction held too.", "whyItMatters": "This is the measured argument for the control being mandatory. A one-armed pin passes in full colour against a gate that refuses nothing whatsoever — it would have reported coverage of a boot decision no longer being made. The control is the arm that goes red there.", "mutationProvenOnDisk": "Same anchored grep -cF discipline, counts read before any result: original 1->0, mutation 0->1.", "restore": "Same trap-based restore; RESTORE_DIFF_LINES=0, verified, and `git status` clean afterwards." } ], "openQuestions": [], "out_of_scope_findings": [ "NOT FILED — dedup channel unavailable from this seat, flagging for PM disposal rather than filing blind. Direct REST (api.github.com) returns 'GitHub access is not enabled for this session', and the MCP list/search issue tools are the banned GraphQL channel for dedup reads, so I could not run the required search-before-filing. Both items below are self-announcing on EVERY dispatch-gates run for every card, so the PM has near-certainly seen them; filing without a dedup read risked duplicates. (1) OBSERVATION: dispatch-gates reports 9 check families whose declared path population matches NOTHING in the tree — 'THE LAYOUT MOVED under a gate that still spells the old path — a real miss, worth triaging', all @objectstack/spec (check:api-surface, check:dual-source-exports, check:entry-nameability, check:export-origins, check:exported-any, check:meta-url-spelling, check:spec-changes, check:strictness-ledger, check:upgrade-guide). They score a quiet green for every card whether they still work or not. (2) OBSERVATION: check:type-check-debt reports @objectstack/runtime TEST_DEBT records 227 while tsc now measures 226 (-1); the gate itself calls this 'not an error' but notes 1 new error can land in that layer while the gate still reports success. Closed with `pnpm check:type-check-debt --lower`.", "NOT a finding, recorded so it is not mistaken for one: the sibling suite already contains a generic needs_confirm case built from a hand-stamped entry. It is correct and was left untouched; it simply cannot observe the emitter, which is exactly the gap this card fills." ] }Generated by Claude Code
Generated by Claude Code
os-warren commentedon Aug 26, 2026
os-dev-report
restorefields there (trap '...' EXIT INT TERMlost its argument, andgit checkout ... -- ...lost both), which is why this repost spells every placeholder without angle brackets. This comment carries the authoritative report; the earlier one differs only by those deletions.{ "issue": 11728, "branch": "claude/issue-11728-artifact-boot-gate-pin", "pr": "https://github.com/objectstack-ai/objectstack/pull/12598", "status": "done", "premise_still_valid": true, "clause2": "no", "clause2_evidence": "git diff --stat vs merge-base b72db0198 (three-dot): '1 file changed, 190 insertions(+)' — 0 deletions, and the single path is packages/cli/src/utils/artifact-boot-migration.report-only-drift.test.ts, a .test.ts. No production file is touched, so there is no surface on which accept/reject behaviour could move. Confirmed on the final commit 0f99e0693b with a clean `git status`. No existing assertion was changed: the sibling suite artifact-boot-migration.test.ts is untouched (0 deletions).", "gateTableReverified": "yes — re-derived by symbol on current origin/main, not from the card's line citation. `runArtifactBootMigrationGate` is at packages/cli/src/utils/artifact-boot-migration.ts:109 and the category read at :137 (`drift.filter((d) => d.category === 'destructive')`); the card's :137 citation happens to still land on it, but was re-located rather than trusted. All three rows reproduce, measured through the real gate function: destructive -> ok=false; needs_confirm -> ok=true; safe -> ok=true. The severity claim also holds: `grep -n severity` exits 1 (NO matches) in BOTH artifact-boot-migration.ts and commands/serve.ts, with the exit captured before any pipe. Stronger than the card's table in one respect: both non-safe rows are now REAL emitter output from diffManagedTable rather than hand-stamped entries, and the severities run OPPOSITE to the boot consequence (the entry that boots is severity 'error'; the entry that refuses is severity 'warning').", "summary": "Adds packages/cli/src/utils/artifact-boot-migration.report-only-drift.test.ts: three cases that drive REAL drift entries (built by @objectstack/driver-sql's diffManagedTable, not hand-stamped) through runArtifactBootMigrationGate. (1) the #11535 stale-column entry — op manual_column_type_change, category needs_confirm, severity error — does NOT refuse an artifact-pinned boot; (2) the required CONTROL: a real destructive entry (orphaned column, op drop_column, severity warning) still refuses, so the pin cannot pass against a gate that has stopped refusing anything; (3) with both present the refusal names the orphan alone and the stale entry still travels to the driver as ordinary non-destructive work. Using the real emitter is what makes this more than a restatement of the category string: the day schema-drift.ts emits this op as destructive, the pin goes red. No production change was needed — the gate's contract is observable from outside, so clause 2 stays 'no'. The pin lives in packages/cli because the gate does; the enabling edge already runs cli -> driver-sql (a CLI test already imports that drift surface at run time), so no package edge and no KNOWN_UNALIASED_TEST_IMPORTS entry was added.", "tests": "All figures from final commit 0f99e0693b (clean tree); gate union derived at that same commit via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` over the real changeset (1 path, three-dot vs merge base b72db0198). Every exit captured with NO pipe between command and $?; each gate quoted by its OWN verdict line, never a bare $?. BASELINE: `pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2` over both artifact-boot-migration suites -> 'Test Files 2 passed (2) / Tests 14 passed (14)', VITEST_EXIT=0, wrapper 'VERDICT command-exit 0'. TYPECHECK: `pnpm --filter @objectstack/cli typecheck` EXIT=0 with the script echoed ('@objectstack/cli@17.2.0 typecheck' / 'tsc --noEmit') — checked against the zero-match trap, it really ran. Selected gate verdict lines: check-test-source-alias 'OK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/'; check-engine-double-contract 'OK — 689 pinned, 134 in the DEBT ledger, 3 exempt'; cross-package-test-inputs 'OK: 20 package(s) read outside themselves, all declared'; where-matcher and query-options-erasure both 'baseline key set verified against b72db01: no files added'; check-type-check-coverage --re-measure 'OK — 31 ledger entr(ies) re-measured in 240.2s, 1687 raw tsc error(s) total, none above its recorded number'; check-nul-bytes 'OK (scanned 6948 text file(s) ... no raw ASCII control bytes)'; check-i18n-bundles 'OK (9 package(s) — all bundles in sync)'; check-i18n-coverage 'OK (12 config(s), 602 baselined untranslated string(s), none new)'. All heavy runs went through scripts/pm/os-verify-lock.sh (waits up to 7m33s were observed and spent on lock-free work, never on an idle round).", "gates": { "green": [ "pnpm --filter @objectstack/cli typecheck (exit 0, script echoed)", "affected suites: artifact-boot-migration.test.ts + artifact-boot-migration.report-only-drift.test.ts (14/14)", "check:cross-package-test-inputs", "node scripts/check-cross-package-test-inputs.mjs (ci.yml spelling)", "check:i18n", "check:i18n-coverage", "check:objectql-double-limit", "check:page-declaration-shape", "check:published-files", "check:slot-lookup", "check:test-source-alias", "check:type-source-resolution", "node scripts/check-ci-filter-parity.mjs", "node scripts/check-comment-mask-adoption.mjs", "node scripts/check-plugin-teardown-shape.mjs", "node scripts/docs-audit/check-affected-docs.mjs", "node scripts/docs-audit/check-drift-comment.mjs", "check:query-options-erasure (convention: adds a test file)", "check:type-check-coverage (convention)", "check:type-check-debt --re-measure (convention; needed the workspace closure built)", "check:engine-double-contract (convention)", "check:where-matcher (convention)", "check:nul-bytes" ], "red": [], "notMeasured": [ "The FULL @objectstack/cli package suite (185 files, ~13 min per its own vitest.config.ts measurements) — DECLARED NARROWING, not an omission: the two affected files were run in full and CI runs the package suite anyway. Nothing else in the union was left unmeasured.", "Recorded because the distinction matters: check:i18n and check:i18n-coverage FIRST returned exit 1 as 'PREREQUISITE NOT MET' / 'COULD NOT MEASURE' on this fresh worktree (unbuilt CLI, then unbuilt connector-mcp/connector-openapi). That is NOT MEASURED, not red — both gates say so in their own text ('Nothing was checked'). Both were then re-measured GREEN after building, and are reported under green as measured results." ] }, "ablations": [ { "name": "A — the #11728 risk realised: the gate starts refusing needs_confirm", "mutation": "packages/cli/src/utils/artifact-boot-migration.ts:137 `d.category === 'destructive'` becomes `d.category !== 'safe'`", "predicted": "RED, exactly 3 failures / 14: the existing 'applies needs_confirm alongside safe', plus my new pin case, plus my new combined case. The destructive CONTROL predicted to stay GREEN (insensitive to this mutation by design).", "observed": "EXACT MATCH — 'Tests 3 failed | 11 passed (14)', VITEST_EXIT=1, and precisely the three predicted test names. Control stayed green as predicted.", "mutationProvenOnDisk": "Anchored grep -cF counts read BEFORE any result: original 1 then 0, mutation 0 then 1; plus `git diff --stat` (1 insertion, 1 deletion) and the full hunk. The mutator REFUSES to write unless the anchor matches exactly once (exit 3), so a zero-hit no-op cannot masquerade as a run.", "rebuild": "None needed, justified by IMPORT FORM not assertion: both suites reach the gate as './artifact-boot-migration.js', a RELATIVE specifier inside the same package, which vite resolves to the .ts SOURCE — no exports field, no dist consulted. (The new suite's BARE '@objectstack/driver-sql' import does resolve through exports to dist and was built beforehand; it is not mutated by either ablation.) Both ablations predicted RED, so a mutation failing to reach the running code would surface as all-green rather than hide.", "restore": "trap RESTORE_FUNCTION EXIT INT TERM; restore is `git checkout THIS_BRANCH -- THE_GATE_PATH` (never `git checkout HEAD -- .`), verified with RESTORE_DIFF_LINES=0, original line present 1, mutation line present 0." }, { "name": "B — the gate stops refusing ANYTHING (the false green the control exists for)", "mutation": "packages/cli/src/utils/artifact-boot-migration.ts:137 `d.category === 'destructive'` becomes `d.category === ('nonexistent_category' as any)` (a stale read of a renamed vocabulary)", "predicted": "RED, exactly 4 failures / 14: the two existing destructive cases, my CONTROL, and my combined case — and CRUCIALLY my pin case predicted to stay GREEN. Also predicted green: 'never applies a destructive change on its own authority' (the destructive entry is excluded from the safe half too by a separate unmutated filter, so the driver is never called and .every() over an empty array is vacuously true).", "observed": "EXACT MATCH — 'Tests 4 failed | 10 passed (14)', VITEST_EXIT=1, precisely the four predicted names, and the pin case is absent from the failure list, i.e. it passed. The vacuous-.every() prediction held too.", "whyItMatters": "This is the measured argument for the control being mandatory. A one-armed pin passes in full colour against a gate that refuses nothing whatsoever — it would have reported coverage of a boot decision no longer being made. The control is the arm that goes red there.", "mutationProvenOnDisk": "Same anchored grep -cF discipline, counts read before any result: original 1 then 0, mutation 0 then 1.", "restore": "Same trap-based restore; RESTORE_DIFF_LINES=0, verified, and `git status` clean afterwards." } ], "openQuestions": [], "out_of_scope_findings": [ "NOT FILED — dedup channel unavailable from this seat, flagging for PM disposal rather than filing blind. Direct REST (api.github.com) returns 'GitHub access is not enabled for this session', and the MCP list/search issue tools are the banned GraphQL channel for dedup reads, so I could not run the required search-before-filing. Both items below are self-announcing on EVERY dispatch-gates run for every card, so the PM has near-certainly seen them; filing without a dedup read risked duplicates. (1) OBSERVATION: dispatch-gates reports 9 check families whose declared path population matches NOTHING in the tree — 'THE LAYOUT MOVED under a gate that still spells the old path — a real miss, worth triaging', all @objectstack/spec (check:api-surface, check:dual-source-exports, check:entry-nameability, check:export-origins, check:exported-any, check:meta-url-spelling, check:spec-changes, check:strictness-ledger, check:upgrade-guide). They score a quiet green for every card whether they still work or not. (2) OBSERVATION: check:type-check-debt reports @objectstack/runtime TEST_DEBT records 227 while tsc now measures 226 (-1); the gate itself calls this 'not an error' but notes 1 new error can land in that layer while the gate still reports success. Closed with `pnpm check:type-check-debt --lower`.", "NOT a finding, recorded so it is not mistaken for one: the sibling suite already contains a generic needs_confirm case built from a hand-stamped entry. It is correct and was left untouched; it simply cannot observe the emitter, which is exactly the gap this card fills.", "PROCESS OBSERVATION for the PM, no issue filed: the `<!-- os-dev-report -->` marker the dev contract prescribes does NOT survive this repo's comment sanitizer — measured on this card by reading the comment back immediately after writing it. Any seat that writes it and does not read back leaves a report the PM's marker scan cannot see." ] }Generated by Claude Code
Generated by Claude Code