Repository navigation
[finding][spec] strictness-ledger.test.ts's z.object(…).strict() fixture is a borrowed live site on a treadmill — it has moved three times in three PRs, and the #4001 campaign's own direction is to convert every remaining carrier #6940
Description
Activity
os-project-manager commented
on Aug 9, 2026 CollaboratorAuthorMore actionsTriage (maintainer-authorized one-off pass, 2026-08-09, registered on #6015): promoted
finding→pm:queue(domain:spec-toolingkept). The cost is measured, not projected — three fixture evictions in three PRs, and the #4001 campaign guarantees more. Deliverable: give the ledger test an owned.strict()fixture (a synthetic carrier under the test's own control, outside the campaign's conversion surface) so the "older spelling still reads as strict" regression cover stops riding live sites. One PR, no gate changes.
Generated by Claude Code
Claim: PM loop round 3
Session:session_01KJATVrh6V2ysutYUJigh3B
Branch:claude/issue-6940-owned-strictness-fixture
Worktree:objectstack-issue-6940
Domain:domain:spec-tooling
File surface:packages/spec/scripts/strictness-ledger.test.tsand, if the fixture needs a file of its own, a source under the test's own directory. ⛔packages/spec/src/**and the strictness-ledger counts artifact (docs/audits/…-strictness-ledger.counts.md, anos-regen-routed generated file) are both OFF this card. Stop on breach; explain in the report.
Container & model: M,mode:subagent,model: opus— keeping the live-site reading as a control while moving the load onto an owned fixture is a design call, not a move.
Serial constraints cleared:strictness-ledger.test.tsandlib/strictness-ledger.tslast touched by44d677c93(#6805 via PR #6935) — the PR that evicted the fixture for the third time; nothing since. No open PR touches either path. This lane's other in-flight card (#7159) isscripts/check-driver-conformance.mjs— disjoint.Premise re-verified live on
origin/mainbefore claiming: the borrowed fixture is stillPerOperationRequiredPermissionsSchema(:186), and the case "reads the OLDERz.object(…).strict()spelling as strict too" is still at:164riding it. The treadmill is intact.⚠️ This card should have gone out in round 2 and did not — my error, recorded here rather than quietly fixed. In round 2 I read this card, concluded in my own analysis that the fix lands inpackages/spec/scripts/**and is therefore squarely this seat's, and then claimed four cards without it. I then reported the lane queue as fully drained. It was not: the live query returned five, I dispatched four. The predecessor's flag ("touches the strictness-ledger forbidden zone — ownership undecided") is what I had resolved, and having resolved it I dropped the card between the analysis and the action. No one else was blocked by it, but the report I gave was wrong and the correction belongs on the card as much as in chat.Dispatched on triage's deliverable, quoted so it is not re-derived: give the ledger test an owned
.strict()fixture — a synthetic carrier under the test's own control, outside the #4001 campaign's conversion surface — so the "older spelling still reads as strict" regression cover stops riding live sites. One PR, no gate changes.
Generated by Claude Code
ACCEPT — PR #7238, reviewed against GitHub rather than against the report.
Verified independently: 1 file (
packages/spec/scripts/strictness-ledger.test.ts, +127/−62).packages/spec/src/**untouched, theos-regen-routed counts artifact untouched, nodocs/adr/**, nocontent/docs/releases/— I checked all four forbidden zones against the diff's own path list rather than takinggit status --porcelainon trust. First line isFixes #6940, correct.skip-changesetpresent and read back.The design call, which I am accepting deliberately rather than waving through
The dev dropped the named live-site pins (
PerOperationRequiredPermissionsSchema, theObjectCapabilities/TenancyConfigSchemaloop, thepermission.zod.tsstrip-count) and replaced them with a population reading. My dispatch said ⛔ do not delete the live-site reading, and the card said the same. So this needs a ruling rather than a nod.It is within the constraint, and it is better than what I asked for. The constraint's purpose was that the assertion stay connected to reality; the constraint's letter was "keep the live-site reading". What landed keeps a live reading of the real tree — it just names no site. And the reasoning is the card's own thesis turned one notch further: naming sites is precisely what put the fixture on the treadmill, so a control that names none of them is the only kind that cannot be evicted. Replacing three evictable named pins with one non-evictable population statement is the fix generalising itself, not scope creep.
What the population control actually asserts is stronger than the pins it replaced:
- every site the reader calls
z.object+strictlives in a file whose text really contains.strict()— corroboration that deliberately does not share the AST reader, so it is an independent check rather than the reader agreeing with itself; - anti-collapse: the tree yields more than one idiom and more than one posture, so a reader that stopped distinguishing either fails.
Terminal state holds, and was checked rather than asserted. The corroboration is written as a loop over a possibly-empty list rather than
toBeGreaterThan(0)— an empty carrier population is the campaign's declared goal, not a regression. Measured today: 437 sites, 5 livez.object(…).strict()carriers, so it is not iterating over nothing right now either.The anti-vacuity check is what makes this acceptable at all, and it landed on the right side: two independent mutations of the reader, and both went RED on the owned fixture, not on the live control. A synthetic fixture the reader cannot fail on would have been worse than the borrowed site it replaced; this one is demonstrably red-capable in the terminal state.
My hypothesis was half wrong, and the falsification is the useful half
I proposed
mutate()as the vehicle. Measured:mutate()cannot express this case — it requires an existing source plus an anchor string, and this case needs a carrier that never existed. Rather than force it, the dev extracted the temp-file machinery underneath asanalyzeSource, mademutate()delegate to it, and folded a third pre-existing inline copy onto the same helper. Three copies of mkdtemp/write/analyze/rm became one, no behaviour change. That consolidation is past the card's letter; it is disclosed, confined to the same file, and is the honest consequence of the falsification rather than a rider.One environment finding worth keeping
The dev reports that unauthenticated
curltoapi.github.comis not available in this container, and that the prescribed REST pre-read of PR labels therefore silently returned an empty array parsed out of an error payload. That is a zero that means "the reader is broken", not "there are no labels" — the same shape as ㉜ and ㉟, arriving through a third door. The real read-back came from a PR-resolving tool instead. Recorded on the seat card so the next dispatch does not prescribe a check that fails open.Undrafting and queueing wait for the gate-bearing job conclusions to read
success.
Generated by Claude Code
- every site the reader calls
- added a commit that references this issue
on Aug 17, 2026
Observation-class. Found while implementing #6805 (PR #6935), which moved this fixture for the third time. Filed unassigned, no
pm:queue— nothing a user hits today; this is a dated fuse under a gate's regression cover.The mechanism
packages/spec/scripts/strictness-ledger.test.tshas a case named "reads the OLDERz.object(…).strict()spelling as strict too". It exists because the ledger's AST posture reader must keep making that reading —packages/specis not the only tree it reads — and astrictObject(-only count readsautomation/as 0 strict when it has 8.To make the reading, the test picks a real live site in the tree and asserts its
posture/idiom. That site is borrowed, not owned. And the #4001 campaign's entire direction is to convert every remainingz.object(shape, { error }).strict()carrier tostrictObject. So each conversion wave evicts the fixture:security/permission.zod.ts(four sites)TenancyConfigSchema(data/object.zod.ts)ObjectCapabilities(same file)PerOperationRequiredPermissionsSchema(same file)Three moves in three PRs. The test's own comment handles each move correctly ("move this fixture rather than deleting the assertion"), and every author so far has obeyed it — so this is not a defect report against any of them.
Why it is still worth recording
Two properties make the treadmill different from ordinary churn:
data/object.zod.tsstill has otherz.object(…).strict()sites. The campaign's stated goal leaves fewer each wave. The terminal state is a tree with zero in-repo carriers, at which point the instruction "move it" has nowhere to point and the next author faces exactly the choice the comment forbids — delete the assertion — with no third option written down.PerOperationRequiredPermissionsSchemawas chosen deliberately: it carries noguidance/aliasestable, so nothing pulls it toward the helper the way a prescription table does. That is a reason it is unlikely to be converted, not a reason it cannot be. It remains an authorable surface the ratchet may reach on other grounds.Shape of a fix (non-binding)
The reading under test is a property of the reader (
scripts/lib/strictness-ledger.ts), not of any file in the tree. Theposture readingdescribe block already has the machinery for this: itsmutate()helper analyses a written-out temp copy of a source, and two neighbouring cases (reads the strictObject( helper as strict — and as strip once un-converted,reads a default site as strip — and as strict once closed) use it to make a reading over source the tree does not have to contain.So the same case could read a small owned fixture (a temp file, or a
__fixtures__source) spellingz.object(shape, { error }).strict(), with the live-site assertion kept alongside as a control while any carrier survives — which preserves what the borrowed site really buys (evidence the idiom exists in the wild) without making the gate's cover depend on it.Not in scope / dedup
strictUnknownKeyError直调点批量迁到strictObject,棘轮降到 0(路线 1 消不掉手抄数组与 shape 的漂移) #5593, [spec] Fold the three hand-written unrecognized_keys error maps intostrictObjectguidance — requires a set-keyed guidance form, and closes the alias-integrity gate's blind spot (#6416 direction 2) #6619/PR refactor(spec): 三个手写 unrecognized_keys 错误映射折叠进 strictObject 的按集合取键 guidance(#6619) #6804, or [finding][spec] #6619's inventory of hand-written$ZodErrorMaps was two short —strictToolErrorandstrictCapabilitiesErrorsurvive the fold, still invisible toalias-integrity.test.ts#6805/PR refactor(spec): 折叠 #6619 漏掉的两个手写 unrecognized_keys 映射,并把闭合钉从实例拓宽为类(#6805) #6935 — each moved the fixture as instructed and each added or kept a control.strictness ledger fixture z.object strict,strictness-ledger.test.ts posture reader fixture,strictness ledger fixture borrowed site): zero open hits.packages/spec/scripts/, and the suggested change is to gate machinery rather than to an authoring surface — hencedomain:spec-tooling. Triage decides.Provenance
Measured at PR #6935's head while relocating the fixture; the eviction history above is read from the test's own comment chain plus the #5593 / #6619 / #6805 diffs.