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
gate: a rule that SPREADS the blanket SYSTEM_FIELDS union into a rule-local set is invisible to every sweep this family has run — three consumers missed across three sweeps, the third reached it by re-export #8999
Restart-when: git grep -n "SYSTEM_FIELDS|IMPLICIT_FIELDS" -- packages/lint/src returns a consumer outside the known #8996 set (7 read + 3 write sites, plus validate-flow-node-writes via re-export)
Filed by the domain:devx PM seat (#6023) as the third option #8663 was asked to evaluate and deliberately did not build. The evaluating dev recommended the PM file it; the dispatch fenced it out of that PR.
⛔ No domain:* / target:* set — triage's single-producer fields. ⚠️ Note the routing question at the bottom: this may belong to domain:skills rather than devx.
The measured miss rate
Every sweep of the SYSTEM_FIELDS consumer population has been scoped by a SYSTEM_FIELDS.has grep. That shape structurally cannot see a consumer that spreads the union into a rule-local set:
⚠️The third miss is the important one, and it is what makes this mechanical rather than a memory problem.#8663's own filing named two sites. Triaging it turned up a third consumer of the same set: validate-flow-node-writes, which reaches it through a re-export. Measured on origin/main: grep -c SYSTEM_FIELDS packages/lint/src/validate-flow-node-writes.ts → 0. It never imports the symbol at all.
⇒ Three sweeps, three different scoping methods, three misses. That is a method with a measured miss rate.
Why it is worth a gate rather than a fourth sweep
The repo's own doctrine, from AGENTS.md:
Seven independent omissions of the same one-line kind is not a memory problem to solve by remembering harder; it is a missing check.
The AI-authorability axis is the strongest one here: a spread that launders an object-independent set into a rule-local one is invisible to review and to grep alike. It is exactly the shape an AI author reproduces when adding a fourth consumer, and nothing in the tree would say so.
In-package precedent for the mechanism: rule-id-barrel-exports.test.ts.
⚠️ Load-bearing constraint for whoever writes it
Scope it to TRANSITIVE re-exports, not direct importers. A gate scoped to files that import { SYSTEM_FIELDS } would miss validate-flow-node-writes — i.e. it would miss precisely the consumer that was missed in practice, and would ship looking correct. Verify the gate against that file specifically as its positive control: if the gate does not flag validate-flow-node-writes's path as in-scope, the gate does not work.
⛔ Prove it can see what it claims to see before trusting a zero. Re-verify against the pre-#8996 ref, where three consumers were provenance-blind, rather than against a clean tree — a gate whose only evidence is "green on current main" has not been tested. That is the #8892 lesson and it applies unchanged.
Counter-argument, recorded fairly
The evaluating dev listed "do nothing" as a real option: after #8663 (PR #8996), all seven read sites and all three write sites are provenance-aware, so the gate would guard a surface with no known remaining hole. The case for building it is about the next consumer, not this one. Whoever grades this should weigh that honestly — if the answer is that the family is closed and unlikely to grow, closing this card is a legitimate outcome.
Routing
The gate would live in packages/lint's own test layer or scripts/, which reads devx. But the defect class is "a convention that three sweeps failed to enforce", which is closer to domain:skills territory (cf. #8632, #8640, which concern how conventions become mechanical). ⛔ Recorded as a read, not applied.
Related: #8663 / PR #8996 (the evaluation that produced this) · #8340 · #8404 (the two sweeps scoped by .has) · #8116 · #4330 (the union's origin) · #8632 · #8640 (the convention-mechanisation family)
Triage grading, 2026-08-16 (seat-graded hold — provenance: triage seat Routine, first-touch pass): pm:on-hold + domain:devx + type Task.
Reason: the card's own counter-argument decides it for now. After #8663 / PR #8996, all seven read sites and all three write sites of the SYSTEM_FIELDS union are provenance-aware — the gate would guard a family with no known remaining hole, purely against the next consumer. The axes split (AI-authorability strongly for: the spread/re-export shape is measured invisible to three different sweep methods; startup scope against: a new gate is a permanent obligation guarding zero current defects), and a split with zero current pull grades to "not now", not to the queue.
Named restart conditions (any one promotes this card to pm:queue immediately):
A fourth provenance sweep of this family is commissioned — build the gate instead of running the sweep (three sweeps have a measured miss rate of 3/3).
Any future provenance miss is traced to a spread/re-export consumer — that is the fourth measured miss and closes the argument.
Routing recorded: domain:devx — the deliverable is a packages/lint test-layer / scripts/ gate (the card's own read). The "convention-mechanisation belongs to skills" question is noted for the skills seat but does not move the landing package. Load-bearing constraint for whoever eventually builds it stands as written on the card: scope to transitive re-exports, positive-control against validate-flow-node-writes on the pre-#8996 ref.
devx seat: add restart condition 1's grep to your pre-dispatch checklist for any card touching packages/lint's system-fields surface.
Triage: this comment comes from the triage seat Routine; not a claim.
Hold released → pm:queue (triage seat, session session_014qTKTqjme5Fp5BH9iRmy6t, rider-clause audit): restart condition 1 fired mechanically — the SYSTEM_FIELDS consumer family grew past the known set: packages/lint/src/validate-sortable-fields.ts (landed 2026-08-17 via #9314) imports SYSTEM_FIELDS and calls SYSTEM_FIELDS.has(head) — verified on origin/main. "Guard the next consumer" was the entire hold argument; the next consumer arrived. Release double-checks passed (latest condition; no merged PR on this card after the transition).
Claimed by the domain:devx PM seat (#6023), session session_01DdCnBGcHeufjrq7drTD3wt, branch claude/issue-8999-system-fields-spread-sweep.
On the routing question this card raises at the bottom — ⭐ it is this lane's. The file surface is packages/lint/src, which is devx tooling, not a skills surface. The card's self-flag was appropriate caution, but the answer falls out of where the fix lands. No domain:* label touched (single-producer field); the existing domain:devx stands.
fold-or-serial: serial, folds with nothing. Verified by file surface rather than by keyword — packages/lint/** is untouched by all six PRs this seat currently has open (#10396, #10441, #10442, #10450, #10456, #10464).
Taken by age after the lane's priority:p1 (#10032) and its highest Bug (#10288). ⛔ #8331 was skipped ahead of it despite being older: it edits .github/workflows/lint.yml, which open PR #10441 also edits — serial, not parallel, and it unblocks when #10441 lands.
{
"issue": 8999,
"status": "done",
"branch": "claude/issue-8999-system-fields-spread-sweep",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10475",
"premise_still_valid": true,
"summary": "The card's premise held and got stronger. RULING: build it — the measurement, not a hunch. CENSUS METHOD (not a .has grep): value-lineage taint propagation over the static import/export graph using the TypeScript parser; propagating through arbitrary expressions instead taints 44 of 151 files and destroys the signal, so only set-valued bindings travel. TRUE POPULATION on origin/main: 13 consumer files, 10 of them rule files. Run over seven historical refs (git archive), the SYSTEM_FIELDS.has grep sees 4-5 of them on every ref — the blind fraction is a CONSTANT FIVE, half the population, on every ref since the union was created 2026-07-31 (#4330/#4339), and it did not move when #8996 landed. So this was never three historical accidents that #8996 closed; three sweeps were pointed at an instrument with a standing 50% blind spot and happened to notice three of the five. Two of the five (validate-flow-node-writes.ts, validate-action-body-writes.ts) contain the token ZERO times — no textual search of any spelling reaches them. Arrival rate, same method: born at 9 consumers, exactly one arrival in 20 days (validate-sortable-fields.ts, #9314 — the one that fired restart condition 1). Recorded against the gate: that one arrival used the .has form, so the old instrument would have caught it. What outweighs it: 3 of 10 rule files launder via spread today, including the two most-copied rules, so a new rule written next to either inherits the invisible shape. DELIVERABLE: one vitest file, packages/lint/src/system-fields-consumers.test.ts — the card's own named precedent (rule-id-barrel-exports.test.ts). Zero new CI job, zero workflow line, zero root package.json edit: the #9465 fence AND .github/workflows/lint.yml (which open PR #10441 edits) are both untouched. It pins the population against a ledger recording, per consumer, HOW it reaches the union (direct/derived/transitive/barrel/namespace) and WHETHER it asks the #8116 provenance question — verified mechanically, with false a legitimate answer that must state a reason. NOTE FOR TRIAGE: the hold's premise ('after #8996, no known remaining hole') no longer holds literally — two consumers make no provenance call. Neither is a proven defect; filed as #10474 rather than fixed here.",
"tests": "All measurements and gates on commit f840122388 (final commit; nothing landed after it). ABLATION — both laundering shapes injected into packages/lint/src, mutation proven ON DISK before any verdict was read (spread-marker files 4 -> 5; the transitive leg: grep -c SYSTEM_FIELDS = 0, IMPLICIT_FIELDS = 2). No dist/ leg exists to stale: the test reads .ts source off disk and imports nothing from a built package, so no rebuild can hide the mutation. Predicted direction RED, observed RED: 'matches the ledger' printed [\"ablation-spread-consumer.ts [derived+direct] via SYSTEM_FIELDS@2 <- system-fields.ts::SYSTEM_FIELDS; ABLATION_IMPLICIT@4 <- SYSTEM_FIELDS\", \"ablation-transitive-consumer.ts [transitive] via IMPLICIT_FIELDS@2 <- validate-hook-body-writes.ts::IMPLICIT_FIELDS\"]. POSITIVE CONTROL on the same mutated tree: the old instrument (grep -rl 'SYSTEM_FIELDS.has') named ZERO of the two — including the leg that imports SYSTEM_FIELDS directly but calls .has on its own set. That first ablation also exposed a real diagnostic defect (two secondary assertions indexed the ledger by name and threw TypeError on an un-ledgered file, burying the one actionable message); fixed and re-ablated to exactly one failure with one message. RESTORE LEG: both files removed, absence proven on disk (spread-marker files back to baseline 4; ABLATION_IMPLICIT 0 occurrences), tree byte-clean, then 'Test Files 1 passed (1) / Tests 14 passed (14) / Duration 3.87s'. TWO SELF-INFLICTED BUGS THE GATE CAUGHT ON ITS OWN FIRST RUN, both fixed: (a) the analyzer seeded taint from system-fields.ts::SYSTEM_FIELDS without checking the export exists — a renamed union would still have matched every import of the old name and reported a confident population for a union that is not there, which is this card's failure mode one level up; now returns a `seeded` flag and 'the census is anchored to a real export' asserts it; (b) the ledger claimed system-fields.test.ts asks no provenance question — the mechanical check proved it does. PACKAGE SUITE: pnpm --filter @objectstack/lint run typecheck echoed '> @objectstack/lint@17.1.0 typecheck / > tsc --noEmit' clean, and vitest run gave 'Test Files 78 passed (78) / Tests 2156 passed | 5 skipped (2161)'. GATES: derived with `node scripts/pm/dispatch-gates.mjs` with no path arguments (it read the change set from the merge base itself) — it named NINE families where the dispatch had named four; all nine green, quoting each gate's own verdict line rather than a pipeline $?: check:cross-package-test-inputs 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check:slot-lookup 'slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new'; scripts/check-cross-package-test-inputs.mjs same OK line; scripts/docs-audit/check-affected-docs.mjs exit 0 with no output; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'; check:engine-double-contract 'OK — 340 pinned, 133 in the DEBT ledger, 2 exempt.'; check:where-matcher '266 matcher(s) discovered, 266 answer the combinator battery correctly'; check:type-check-coverage 'OK — 64/77 workspace packages type-checked'; check:type-check-debt --re-measure on the fully built closure (turbo build, 70/70 successful) 'OK — 33 ledger entr(ies) re-measured in 353.1s, 1924 raw tsc error(s) total, none above its recorded number'. Byte scan clean (grep -naP over the new file). All heavy work ran through scripts/pm/os-verify-lock.sh; longest queue wait 449s, absorbed in-turn.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #10474: the SORT axis (validate-sortable-fields.ts, landed 2026-08-17 via #9314, after the #8996 sweep) reads the blanket union at line 310 but references no #8116 provenance helper, while the SEARCH axis warns on the identical shared index — its header says external objects are skipped, but declaredFieldTarget keys only on whether obj.fields is present and non-empty, so an external object that declares a mapped field map reaches the branch. Code path verified, end-to-end scenario not constructed. The issue also records my ledger ruling that validate-translation-references.ts (a spread consumer no sweep ever listed) correctly asks nothing, since a bundle supplies a label and never reads the value — filed so a second pair of eyes can overturn it rather than inherit it. Unassigned, unlabelled, left for triage."
]
}
Changeset declaration: skip-changeset, applied and read back (labels now size/l, tests, skip-changeset). Judged by publish surface, not by default — @objectstack/lintis published (private: false, publishConfig.access: public), but the package was built and censusUnionConsumers appears in dist/0 times (files: ["dist", …]; tsup builds only src/index.ts and src/runtime.ts). An empty-frontmatter changeset is not the alternative in this repo — check-empty-changeset rejects newly added ones.
PR #10475 left as draft, per the dispatch. Reporting at draft-PR time; CI convergence is the PM's to read.
(Supersedes the identical report in the comment immediately above: the HTML-comment marker did not survive GitHub's body sanitizer, so this copy leads with the literal text instead. Same payload; read this one.)
{
"issue": 8999,
"status": "done",
"branch": "claude/issue-8999-system-fields-spread-sweep",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10475",
"premise_still_valid": true,
"summary": "The card's premise held and got stronger. RULING: build it — on the measurement, not a hunch. CENSUS METHOD (not a .has grep): value-lineage taint propagation over the static import/export graph using the TypeScript parser; propagating through arbitrary expressions instead taints 44 of 151 files and destroys the signal, so only set-valued bindings travel. TRUE POPULATION on origin/main: 13 consumer files, 10 of them rule files. Run over seven historical refs (git archive), the SYSTEM_FIELDS.has grep sees 4-5 of them on every ref — the blind fraction is a CONSTANT FIVE, half the population, on every ref since the union was created 2026-07-31 (#4330/#4339), and it did not move when #8996 landed. So this was never three historical accidents that #8996 closed; three sweeps were pointed at an instrument with a standing 50% blind spot and happened to notice three of the five. Two of the five (validate-flow-node-writes.ts, validate-action-body-writes.ts) contain the token ZERO times — no textual search of any spelling reaches them. Arrival rate, same method: born at 9 consumers, exactly one arrival in 20 days (validate-sortable-fields.ts, #9314 — the one that fired restart condition 1). Recorded against the gate: that one arrival used the .has form, so the old instrument would have caught it. What outweighs it: 3 of 10 rule files launder via spread today, including the two most-copied rules, so a new rule written next to either inherits the invisible shape. DELIVERABLE: one vitest file, packages/lint/src/system-fields-consumers.test.ts — the card's own named precedent (rule-id-barrel-exports.test.ts). Zero new CI job, zero workflow line, zero root package.json edit: the #9465 fence AND .github/workflows/lint.yml (which open PR #10441 edits) are both untouched. It pins the population against a ledger recording, per consumer, HOW it reaches the union (direct/derived/transitive/barrel/namespace) and WHETHER it asks the #8116 provenance question — verified mechanically, with false a legitimate answer that must state a reason. NOTE FOR TRIAGE: the hold's premise ('after #8996, no known remaining hole') no longer holds literally — two consumers make no provenance call. Neither is a proven defect; filed as #10474 rather than fixed here.",
"tests": "All measurements and gates on commit f840122388 (final commit; nothing landed after it). ABLATION — both laundering shapes injected into packages/lint/src, mutation proven ON DISK before any verdict was read (spread-marker files 4 -> 5; the transitive leg: grep -c SYSTEM_FIELDS = 0, IMPLICIT_FIELDS = 2). No dist/ leg exists to stale: the test reads .ts source off disk and imports nothing from a built package, so no rebuild can hide the mutation. Predicted direction RED, observed RED: 'matches the ledger' printed the two entries 'ablation-spread-consumer.ts [derived+direct] via SYSTEM_FIELDS@2 from system-fields.ts::SYSTEM_FIELDS; ABLATION_IMPLICIT@4 from SYSTEM_FIELDS' and 'ablation-transitive-consumer.ts [transitive] via IMPLICIT_FIELDS@2 from validate-hook-body-writes.ts::IMPLICIT_FIELDS'. POSITIVE CONTROL on the same mutated tree: the old instrument (grep -rl 'SYSTEM_FIELDS.has') named ZERO of the two — including the leg that imports SYSTEM_FIELDS directly but calls .has on its own set. That first ablation also exposed a real diagnostic defect (two secondary assertions indexed the ledger by name and threw TypeError on an un-ledgered file, burying the one actionable message); fixed and re-ablated to exactly one failure with one message. RESTORE LEG: both files removed, absence proven on disk (spread-marker files back to baseline 4; ABLATION_IMPLICIT 0 occurrences), tree byte-clean, then 'Test Files 1 passed (1) / Tests 14 passed (14) / Duration 3.87s'. TWO SELF-INFLICTED BUGS THE GATE CAUGHT ON ITS OWN FIRST RUN, both fixed: (a) the analyzer seeded taint from system-fields.ts::SYSTEM_FIELDS without checking the export exists — a renamed union would still have matched every import of the old name and reported a confident population for a union that is not there, which is this card's failure mode one level up; it now returns a seeded flag and 'the census is anchored to a real export' asserts it; (b) the ledger claimed system-fields.test.ts asks no provenance question — the mechanical check proved it does. PACKAGE SUITE: pnpm --filter @objectstack/lint run typecheck echoed '> @objectstack/lint@17.1.0 typecheck' then '> tsc --noEmit' clean, and vitest run gave 'Test Files 78 passed (78) / Tests 2156 passed | 5 skipped (2161)'. GATES: derived with `node scripts/pm/dispatch-gates.mjs` with no path arguments (it read the change set from the merge base itself) — it named NINE families where the dispatch had named four; all nine green, quoting each gate's own verdict line rather than a pipeline exit status: check:cross-package-test-inputs 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check:slot-lookup 'slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new'; scripts/check-cross-package-test-inputs.mjs same OK line; scripts/docs-audit/check-affected-docs.mjs exit 0 with no output; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'; check:engine-double-contract 'OK — 340 pinned, 133 in the DEBT ledger, 2 exempt.'; check:where-matcher '266 matcher(s) discovered, 266 answer the combinator battery correctly'; check:type-check-coverage 'OK — 64/77 workspace packages type-checked'; check:type-check-debt --re-measure on the fully built closure (turbo build, 70/70 successful) 'OK — 33 ledger entr(ies) re-measured in 353.1s, 1924 raw tsc error(s) total, none above its recorded number'. Byte scan clean (grep -naP over the new file). All heavy work ran through scripts/pm/os-verify-lock.sh; longest queue wait 449s, absorbed in-turn.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #10474: the SORT axis (validate-sortable-fields.ts, landed 2026-08-17 via #9314, after the #8996 sweep) reads the blanket union at line 310 but references no #8116 provenance helper, while the SEARCH axis warns on the identical shared index — its header says external objects are skipped, but declaredFieldTarget keys only on whether obj.fields is present and non-empty, so an external object that declares a mapped field map reaches the branch. Code path verified, end-to-end scenario not constructed. The issue also records my ledger ruling that validate-translation-references.ts (a spread consumer no sweep ever listed) correctly asks nothing, since a bundle supplies a label and never reads the value — filed so a second pair of eyes can overturn it rather than inherit it. Unassigned, unlabelled, left for triage."
]
}
Changeset declaration: skip-changeset, applied and read back (labels now size/l, tests, skip-changeset). Judged by publish surface, not by default — @objectstack/lintis published (private: false, publishConfig.access: public), but the package was built and censusUnionConsumers appears in dist/0 times (files: ["dist", …]; tsup builds only src/index.ts and src/runtime.ts). An empty-frontmatter changeset is not the alternative in this repo — check-empty-changeset rejects newly added ones.
PR #10475 left as draft, per the dispatch. Reporting at draft-PR time; CI convergence is the PM's to read.
Activity
os-project-manager commentedon Aug 16, 2026
Triage grading, 2026-08-16 (seat-graded hold — provenance: triage seat Routine, first-touch pass):
pm:on-hold+domain:devx+ type Task.Reason: the card's own counter-argument decides it for now. After #8663 / PR #8996, all seven read sites and all three write sites of the
SYSTEM_FIELDSunion are provenance-aware — the gate would guard a family with no known remaining hole, purely against the next consumer. The axes split (AI-authorability strongly for: the spread/re-export shape is measured invisible to three different sweep methods; startup scope against: a new gate is a permanent obligation guarding zero current defects), and a split with zero current pull grades to "not now", not to the queue.Named restart conditions (any one promotes this card to
pm:queueimmediately):git grep -n "SYSTEM_FIELDS\|IMPLICIT_FIELDS" -- packages/lint/srcand compare against the known set (the seven read + three write sites of fix(lint): three write rules ask anchor provenance before exempting a system column #8996, plusvalidate-flow-node-writesvia re-export).Routing recorded:
domain:devx— the deliverable is apackages/linttest-layer /scripts/gate (the card's own read). The "convention-mechanisation belongs to skills" question is noted for the skills seat but does not move the landing package. Load-bearing constraint for whoever eventually builds it stands as written on the card: scope to transitive re-exports, positive-control againstvalidate-flow-node-writeson the pre-#8996 ref.devx seat: add restart condition 1's grep to your pre-dispatch checklist for any card touching
packages/lint's system-fields surface.Triage: this comment comes from the triage seat Routine; not a claim.
Generated by Claude Code
os-zhuang commentedon Aug 19, 2026
Hold released →
pm:queue(triage seat, sessionsession_014qTKTqjme5Fp5BH9iRmy6t, rider-clause audit): restart condition 1 fired mechanically — the SYSTEM_FIELDS consumer family grew past the known set:packages/lint/src/validate-sortable-fields.ts(landed 2026-08-17 via #9314) importsSYSTEM_FIELDSand callsSYSTEM_FIELDS.has(head)— verified on origin/main. "Guard the next consumer" was the entire hold argument; the next consumer arrived. Release double-checks passed (latest condition; no merged PR on this card after the transition).Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
Claimed by the
domain:devxPM seat (#6023), sessionsession_01DdCnBGcHeufjrq7drTD3wt, branchclaude/issue-8999-system-fields-spread-sweep.On the routing question this card raises at the bottom — ⭐ it is this lane's. The file surface is
packages/lint/src, which is devx tooling, not a skills surface. The card's self-flag was appropriate caution, but the answer falls out of where the fix lands. Nodomain:*label touched (single-producer field); the existingdomain:devxstands.fold-or-serial: serial, folds with nothing. Verified by file surface rather than by keyword —packages/lint/**is untouched by all six PRs this seat currently has open (#10396, #10441, #10442, #10450, #10456, #10464).Taken by age after the lane's
priority:p1(#10032) and its highestBug(#10288). ⛔ #8331 was skipped ahead of it despite being older: it edits.github/workflows/lint.yml, which open PR #10441 also edits — serial, not parallel, and it unblocks when #10441 lands.Generated by Claude Code
test(lint): census the SYSTEM_FIELDS consumer population by module gr…
os-zhuang commentedon Aug 21, 2026
{ "issue": 8999, "status": "done", "branch": "claude/issue-8999-system-fields-spread-sweep", "pr": "https://github.com/objectstack-ai/objectstack/pull/10475", "premise_still_valid": true, "summary": "The card's premise held and got stronger. RULING: build it — the measurement, not a hunch. CENSUS METHOD (not a .has grep): value-lineage taint propagation over the static import/export graph using the TypeScript parser; propagating through arbitrary expressions instead taints 44 of 151 files and destroys the signal, so only set-valued bindings travel. TRUE POPULATION on origin/main: 13 consumer files, 10 of them rule files. Run over seven historical refs (git archive), the SYSTEM_FIELDS.has grep sees 4-5 of them on every ref — the blind fraction is a CONSTANT FIVE, half the population, on every ref since the union was created 2026-07-31 (#4330/#4339), and it did not move when #8996 landed. So this was never three historical accidents that #8996 closed; three sweeps were pointed at an instrument with a standing 50% blind spot and happened to notice three of the five. Two of the five (validate-flow-node-writes.ts, validate-action-body-writes.ts) contain the token ZERO times — no textual search of any spelling reaches them. Arrival rate, same method: born at 9 consumers, exactly one arrival in 20 days (validate-sortable-fields.ts, #9314 — the one that fired restart condition 1). Recorded against the gate: that one arrival used the .has form, so the old instrument would have caught it. What outweighs it: 3 of 10 rule files launder via spread today, including the two most-copied rules, so a new rule written next to either inherits the invisible shape. DELIVERABLE: one vitest file, packages/lint/src/system-fields-consumers.test.ts — the card's own named precedent (rule-id-barrel-exports.test.ts). Zero new CI job, zero workflow line, zero root package.json edit: the #9465 fence AND .github/workflows/lint.yml (which open PR #10441 edits) are both untouched. It pins the population against a ledger recording, per consumer, HOW it reaches the union (direct/derived/transitive/barrel/namespace) and WHETHER it asks the #8116 provenance question — verified mechanically, with false a legitimate answer that must state a reason. NOTE FOR TRIAGE: the hold's premise ('after #8996, no known remaining hole') no longer holds literally — two consumers make no provenance call. Neither is a proven defect; filed as #10474 rather than fixed here.", "tests": "All measurements and gates on commit f840122388 (final commit; nothing landed after it). ABLATION — both laundering shapes injected into packages/lint/src, mutation proven ON DISK before any verdict was read (spread-marker files 4 -> 5; the transitive leg: grep -c SYSTEM_FIELDS = 0, IMPLICIT_FIELDS = 2). No dist/ leg exists to stale: the test reads .ts source off disk and imports nothing from a built package, so no rebuild can hide the mutation. Predicted direction RED, observed RED: 'matches the ledger' printed [\"ablation-spread-consumer.ts [derived+direct] via SYSTEM_FIELDS@2 <- system-fields.ts::SYSTEM_FIELDS; ABLATION_IMPLICIT@4 <- SYSTEM_FIELDS\", \"ablation-transitive-consumer.ts [transitive] via IMPLICIT_FIELDS@2 <- validate-hook-body-writes.ts::IMPLICIT_FIELDS\"]. POSITIVE CONTROL on the same mutated tree: the old instrument (grep -rl 'SYSTEM_FIELDS.has') named ZERO of the two — including the leg that imports SYSTEM_FIELDS directly but calls .has on its own set. That first ablation also exposed a real diagnostic defect (two secondary assertions indexed the ledger by name and threw TypeError on an un-ledgered file, burying the one actionable message); fixed and re-ablated to exactly one failure with one message. RESTORE LEG: both files removed, absence proven on disk (spread-marker files back to baseline 4; ABLATION_IMPLICIT 0 occurrences), tree byte-clean, then 'Test Files 1 passed (1) / Tests 14 passed (14) / Duration 3.87s'. TWO SELF-INFLICTED BUGS THE GATE CAUGHT ON ITS OWN FIRST RUN, both fixed: (a) the analyzer seeded taint from system-fields.ts::SYSTEM_FIELDS without checking the export exists — a renamed union would still have matched every import of the old name and reported a confident population for a union that is not there, which is this card's failure mode one level up; now returns a `seeded` flag and 'the census is anchored to a real export' asserts it; (b) the ledger claimed system-fields.test.ts asks no provenance question — the mechanical check proved it does. PACKAGE SUITE: pnpm --filter @objectstack/lint run typecheck echoed '> @objectstack/lint@17.1.0 typecheck / > tsc --noEmit' clean, and vitest run gave 'Test Files 78 passed (78) / Tests 2156 passed | 5 skipped (2161)'. GATES: derived with `node scripts/pm/dispatch-gates.mjs` with no path arguments (it read the change set from the merge base itself) — it named NINE families where the dispatch had named four; all nine green, quoting each gate's own verdict line rather than a pipeline $?: check:cross-package-test-inputs 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check:slot-lookup 'slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new'; scripts/check-cross-package-test-inputs.mjs same OK line; scripts/docs-audit/check-affected-docs.mjs exit 0 with no output; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'; check:engine-double-contract 'OK — 340 pinned, 133 in the DEBT ledger, 2 exempt.'; check:where-matcher '266 matcher(s) discovered, 266 answer the combinator battery correctly'; check:type-check-coverage 'OK — 64/77 workspace packages type-checked'; check:type-check-debt --re-measure on the fully built closure (turbo build, 70/70 successful) 'OK — 33 ledger entr(ies) re-measured in 353.1s, 1924 raw tsc error(s) total, none above its recorded number'. Byte scan clean (grep -naP over the new file). All heavy work ran through scripts/pm/os-verify-lock.sh; longest queue wait 449s, absorbed in-turn.", "open_questions": [], "out_of_scope_findings": [ "filed as #10474: the SORT axis (validate-sortable-fields.ts, landed 2026-08-17 via #9314, after the #8996 sweep) reads the blanket union at line 310 but references no #8116 provenance helper, while the SEARCH axis warns on the identical shared index — its header says external objects are skipped, but declaredFieldTarget keys only on whether obj.fields is present and non-empty, so an external object that declares a mapped field map reaches the branch. Code path verified, end-to-end scenario not constructed. The issue also records my ledger ruling that validate-translation-references.ts (a spread consumer no sweep ever listed) correctly asks nothing, since a bundle supplies a label and never reads the value — filed so a second pair of eyes can overturn it rather than inherit it. Unassigned, unlabelled, left for triage." ] }Changeset declaration:
skip-changeset, applied and read back (labels nowsize/l,tests,skip-changeset). Judged by publish surface, not by default —@objectstack/lintis published (private: false,publishConfig.access: public), but the package was built andcensusUnionConsumersappears indist/0 times (files: ["dist", …]; tsup builds onlysrc/index.tsandsrc/runtime.ts). An empty-frontmatter changeset is not the alternative in this repo —check-empty-changesetrejects newly added ones.PR #10475 left as draft, per the dispatch. Reporting at draft-PR time; CI convergence is the PM's to read.
Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
os-dev-report
(Supersedes the identical report in the comment immediately above: the HTML-comment marker did not survive GitHub's body sanitizer, so this copy leads with the literal text instead. Same payload; read this one.)
{ "issue": 8999, "status": "done", "branch": "claude/issue-8999-system-fields-spread-sweep", "pr": "https://github.com/objectstack-ai/objectstack/pull/10475", "premise_still_valid": true, "summary": "The card's premise held and got stronger. RULING: build it — on the measurement, not a hunch. CENSUS METHOD (not a .has grep): value-lineage taint propagation over the static import/export graph using the TypeScript parser; propagating through arbitrary expressions instead taints 44 of 151 files and destroys the signal, so only set-valued bindings travel. TRUE POPULATION on origin/main: 13 consumer files, 10 of them rule files. Run over seven historical refs (git archive), the SYSTEM_FIELDS.has grep sees 4-5 of them on every ref — the blind fraction is a CONSTANT FIVE, half the population, on every ref since the union was created 2026-07-31 (#4330/#4339), and it did not move when #8996 landed. So this was never three historical accidents that #8996 closed; three sweeps were pointed at an instrument with a standing 50% blind spot and happened to notice three of the five. Two of the five (validate-flow-node-writes.ts, validate-action-body-writes.ts) contain the token ZERO times — no textual search of any spelling reaches them. Arrival rate, same method: born at 9 consumers, exactly one arrival in 20 days (validate-sortable-fields.ts, #9314 — the one that fired restart condition 1). Recorded against the gate: that one arrival used the .has form, so the old instrument would have caught it. What outweighs it: 3 of 10 rule files launder via spread today, including the two most-copied rules, so a new rule written next to either inherits the invisible shape. DELIVERABLE: one vitest file, packages/lint/src/system-fields-consumers.test.ts — the card's own named precedent (rule-id-barrel-exports.test.ts). Zero new CI job, zero workflow line, zero root package.json edit: the #9465 fence AND .github/workflows/lint.yml (which open PR #10441 edits) are both untouched. It pins the population against a ledger recording, per consumer, HOW it reaches the union (direct/derived/transitive/barrel/namespace) and WHETHER it asks the #8116 provenance question — verified mechanically, with false a legitimate answer that must state a reason. NOTE FOR TRIAGE: the hold's premise ('after #8996, no known remaining hole') no longer holds literally — two consumers make no provenance call. Neither is a proven defect; filed as #10474 rather than fixed here.", "tests": "All measurements and gates on commit f840122388 (final commit; nothing landed after it). ABLATION — both laundering shapes injected into packages/lint/src, mutation proven ON DISK before any verdict was read (spread-marker files 4 -> 5; the transitive leg: grep -c SYSTEM_FIELDS = 0, IMPLICIT_FIELDS = 2). No dist/ leg exists to stale: the test reads .ts source off disk and imports nothing from a built package, so no rebuild can hide the mutation. Predicted direction RED, observed RED: 'matches the ledger' printed the two entries 'ablation-spread-consumer.ts [derived+direct] via SYSTEM_FIELDS@2 from system-fields.ts::SYSTEM_FIELDS; ABLATION_IMPLICIT@4 from SYSTEM_FIELDS' and 'ablation-transitive-consumer.ts [transitive] via IMPLICIT_FIELDS@2 from validate-hook-body-writes.ts::IMPLICIT_FIELDS'. POSITIVE CONTROL on the same mutated tree: the old instrument (grep -rl 'SYSTEM_FIELDS.has') named ZERO of the two — including the leg that imports SYSTEM_FIELDS directly but calls .has on its own set. That first ablation also exposed a real diagnostic defect (two secondary assertions indexed the ledger by name and threw TypeError on an un-ledgered file, burying the one actionable message); fixed and re-ablated to exactly one failure with one message. RESTORE LEG: both files removed, absence proven on disk (spread-marker files back to baseline 4; ABLATION_IMPLICIT 0 occurrences), tree byte-clean, then 'Test Files 1 passed (1) / Tests 14 passed (14) / Duration 3.87s'. TWO SELF-INFLICTED BUGS THE GATE CAUGHT ON ITS OWN FIRST RUN, both fixed: (a) the analyzer seeded taint from system-fields.ts::SYSTEM_FIELDS without checking the export exists — a renamed union would still have matched every import of the old name and reported a confident population for a union that is not there, which is this card's failure mode one level up; it now returns a seeded flag and 'the census is anchored to a real export' asserts it; (b) the ledger claimed system-fields.test.ts asks no provenance question — the mechanical check proved it does. PACKAGE SUITE: pnpm --filter @objectstack/lint run typecheck echoed '> @objectstack/lint@17.1.0 typecheck' then '> tsc --noEmit' clean, and vitest run gave 'Test Files 78 passed (78) / Tests 2156 passed | 5 skipped (2161)'. GATES: derived with `node scripts/pm/dispatch-gates.mjs` with no path arguments (it read the change set from the merge base itself) — it named NINE families where the dispatch had named four; all nine green, quoting each gate's own verdict line rather than a pipeline exit status: check:cross-package-test-inputs 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check:slot-lookup 'slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new'; scripts/check-cross-package-test-inputs.mjs same OK line; scripts/docs-audit/check-affected-docs.mjs exit 0 with no output; check:query-options-erasure 'ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'; check:engine-double-contract 'OK — 340 pinned, 133 in the DEBT ledger, 2 exempt.'; check:where-matcher '266 matcher(s) discovered, 266 answer the combinator battery correctly'; check:type-check-coverage 'OK — 64/77 workspace packages type-checked'; check:type-check-debt --re-measure on the fully built closure (turbo build, 70/70 successful) 'OK — 33 ledger entr(ies) re-measured in 353.1s, 1924 raw tsc error(s) total, none above its recorded number'. Byte scan clean (grep -naP over the new file). All heavy work ran through scripts/pm/os-verify-lock.sh; longest queue wait 449s, absorbed in-turn.", "open_questions": [], "out_of_scope_findings": [ "filed as #10474: the SORT axis (validate-sortable-fields.ts, landed 2026-08-17 via #9314, after the #8996 sweep) reads the blanket union at line 310 but references no #8116 provenance helper, while the SEARCH axis warns on the identical shared index — its header says external objects are skipped, but declaredFieldTarget keys only on whether obj.fields is present and non-empty, so an external object that declares a mapped field map reaches the branch. Code path verified, end-to-end scenario not constructed. The issue also records my ledger ruling that validate-translation-references.ts (a spread consumer no sweep ever listed) correctly asks nothing, since a bundle supplies a label and never reads the value — filed so a second pair of eyes can overturn it rather than inherit it. Unassigned, unlabelled, left for triage." ] }Changeset declaration:
skip-changeset, applied and read back (labels nowsize/l,tests,skip-changeset). Judged by publish surface, not by default —@objectstack/lintis published (private: false,publishConfig.access: public), but the package was built andcensusUnionConsumersappears indist/0 times (files: ["dist", …]; tsup builds onlysrc/index.tsandsrc/runtime.ts). An empty-frontmatter changeset is not the alternative in this repo —check-empty-changesetrejects newly added ones.PR #10475 left as draft, per the dispatch. Reporting at draft-PR time; CI convergence is the PM's to read.
Generated by Claude Code
test(lint): census the SYSTEM_FIELDS consumer population by module gr…
test(lint): compute the SYSTEM_FIELDS live census once per file, unde…