fix(devx): read NAME.enum.MEMBER so 36 real producers leave the unresolved census - #15736
Conversation
…esolved census `check:error-status-conformance` derives the runtime side of the code/status reconciliation out of source, and REPORTS every declaration it cannot read rather than dropping it. 36 of the 50 declarations in that census were one mechanical shape: a producer stamping `err.code = StandardErrorCode.enum.X` beside a literal status. `buildConstantIndex` recorded nothing at all for a `z.enum([...])` declaration, and `lookup()` walks at most one dot, so the whole family resolved to `undefined` — 36 genuine producers, every one of them with its status already a literal, sat outside the derivation. Index a `z.enum([...])` declaration as `NAME.enum.MEMBER -> 'MEMBER'`, one entry per literal (a Zod enum member's value equals its own name by construction), and let `lookup()` walk that ONE extra segment. A name the array does not list stays absent from the index and its declaration is reported unresolved, never invented; dotted paths in general are still refused, deliberately. Self-test battery 25 pins the shape in all three directions (member resolves, non-member reported, `.enum.` is the only way in); the roster floor and case count move with it: 47 cases -> 50, floor 25 -> 26. Measured on this tree, before -> after: producer sites derived 279 -> 315 (+36, exactly the 36 `.enum.` rows) unresolved declarations 50 -> 14 (0 of them `.enum.` now) reconciled 25 codes / 26 pairs -> unchanged unpinned census 26 (baselined 26) -> unchanged The baseline shrink the card anticipated is EMPTY on today's main, and that is a reading rather than an omission: the 36 sites introduce zero NEW (code, status) pairs. All six codes they name (VALIDATION_ERROR, INVALID_FIELD, INVALID_FILTER, INVALID_QUERY, NOT_IMPLEMENTED, DATABASE_ERROR) already had a derived producer via another rule, and none of the six appears in `scripts/error-status-unpinned-baseline.json`. No row becomes `nowPinned`, so the baseline is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
|
ACCEPT — PM seat Reviewed against the three-dot diff at What the diff does: Measured by this seat on the PR head and on So the 36 mechanical rows left the census (+36 sites), reconciled/unpinned lines byte-identical, baseline unchanged. The dev's set-wise proof (pairs 106 before and after, NEW [], LOST []) and ablation (drop the Fences held: one file; Recorded for triage, not filed (the dev's two observations, handed back on the card): the remaining 14 unresolved rows are three other shapes (7 opaque bare Generated by Claude Code |
Fixes #15644
check:error-status-conformancederives the runtime half of the code/status reconciliation out of source, and its header rule is that a declaration it cannot read is REPORTED, never dropped. It was doing exactly that — and 36 of the 50 rows in that census were one mechanical shape: a producer stampingerr.code = StandardErrorCode.enum.SOME_CODEbeside a status that is already a literal.buildConstantIndexrecorded nothing at all for az.enum([...])declaration, andlookup()walks at most one dot, so the whole family resolved toundefined.What changed
buildConstantIndexindexes az.enum([...])declaration asNAME.enum.MEMBER -> 'MEMBER', one entry per literal — a Zod enum member's value equals its own name by construction, so the array is the value table.lookup()walks one extra segment, for exactly the shapeIDENT.enum.MEMBER. Dotted paths in general are still refused: a general walk would resolve members of objects the index never read, which is the guessing this file exists to avoid. A member the array does not list stays absent from the index and its declaration is reported unresolved, never invented..enum.is the only way in). Roster floor 25 -> 26,CASES47 -> 50, and the green verdict line now names the shape so a run that stopped exercising it cannot print the same sentence.Before / after — re-measured on today's
origin/mainThe card's 50/36 were read on the
#15633branch before it landed, so both legs were re-measured here. BEFORE isorigin/main6c0813196in a detached compare worktree; AFTER is this branch's head6cc01aa88.pnpm check:error-status-conformance, exit code captured before any pipe.6c08131966cc01aa88EXIT=0EXIT=047 cases pass50 cases passscope:producer sites derived279 producer site(s) derived315 producer site(s) derivedscope:codes reconciled52 code(s) reconciled = 50 StandardErrorCode member(s) + 2 ledger code(s)reconciled:26 code(s) with a derived producer, 27 (code, status) pair(s) matchedunpinned:26 documented code(s) with no derivable producer (baselined: 26)unresolved:50 declaration(s) the deriver could not read14 declaration(s) the deriver could not readStandardErrorCode.enum.*every derivable runtime status is documented, and every documented status is reachable+36producer sites is exactly the 36 rows that leftunresolved— proven set-wise, not inferred from the delta: runningbuildConstantIndex/deriveRuntimeStatusesfrom both revisions over the same 2244 sources givesdeclarations that MOVED out of unresolved: 36,all of them .enum.: true, andLOST pairs: [].The anticipated baseline shrink is EMPTY — measured, not skipped
The card and the triage both flagged that this moves a ratchet and that removing the
nowPinnedrows by hand is part of the change. On today's main it moves none, and that is a reading rather than an omission.scripts/error-status-unpinned-baseline.jsonis untouched — no row deleted, no--updaterun — for two independently measured reasons:(code, status)pairs. Same probe as above:pairs BEFORE 106,pairs AFTER 106,NEW (code,status) pairs introduced: []. A baseline row can only becomenowPinnedby gaining a first producer; none does. The gate's ownreconciled:andunpinned:lines agree — both unchanged across the change.VALIDATION_ERROR,INVALID_FIELD,INVALID_FILTER,INVALID_QUERY,NOT_IMPLEMENTED,DATABASE_ERROR(36 sites acrossdriver-sql,driver-mongodb,driver-turso,driver-memory,objectql,formula,service-analytics); intersecting that set with the 26 baseline rows is empty. Each already had a derived producer through another rule, so these sites corroborate pairs that were already reconciled.The control leg the ruling asked for is this run. Indexer change with the baseline left alone is the delivered state:
EXIT=0, zeronowPinnedfindings printed. Had the shrink set been non-empty, this run would have been theEXIT=1that names it. So the removed-row table is empty by measurement — there is no row to point a producer site at, and none was deleted "to be safe".#15708touches this same baseline file; this PR does not touch it at all, so there is nothing to merge there.Ablation — the
.enum.walk is load-bearingTrap-guarded, absolute paths, mutation and restore both proven on disk. No build leg applies: the gate is a plain
.mjsrun from source (scripts/, sibling relative imports), nothing resolves through adist/..enum.alternation fromlookup(). Landed on disk: anchor count1 -> 0,MUT_BLOB=e9be4543…vsHEAD_BLOB=fdd1600a….✗ … 2/50 case(s) failed—x self-test: 25 a z.enum member resolves to its own nameandx self-test: 25c the member is addressable through .enum. and nowhere else. Real run falls back to279 producer site(s)/unresolved: 50with the.enum.subset back at 36. (Case 25b stays green under ablation by construction: it asserts a declaration is reported, which an ablated walk also does. 25 and 25c are the legs that can only pass with the walk present.)REST_BLOBequalsHEAD_BLOBbyte for byte,git diff HEADempty (0 bytes), anchor back at 1, self-test50 cases pass, real run315 producer site(s)/unresolved: 14/.enum.subset 0.Gates — the derived family on the final head
6cc01aa88node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack(change set derived by the tool from git, not hand-fed): 29 commands, all re-run on this head after the rebase, exit code captured before any pipe. All 29 exit 0, including:pnpm check:error-status-conformance—EXIT=0, verdict quoted above.pnpm check:ratchet-remedy-authority—EXIT=0:OK check-ratchet-remedy-authority: 205 scripts swept …; 14 mark the expanding remedy ⛔ MAINTAINER-ONLY, 6 turn it down outright …. The maintainer-only marker on the baseline-EXPANDING remedy is untouched.pnpm check:nul-bytes—EXIT=0:check-nul-bytes: OK (scanned 7594 text file(s) …; no raw ASCII control bytes). Plus a direct scan of the edited file for control bytes: no match.pnpm check:pm-dispatch-gates—EXIT=0,1445 cases pass.Six roster gates whose roster lives under
scripts/were run beyond the derived family (their silence is not a clearance):check-published-list-mirrorsand its self-test,check:console-injection,check:engine-double-contract,check:i18n-stale-fill— allEXIT=0.NOT MEASURED, by name:
pnpm check:published-readme-exports—EXIT=3,check-published-readme-exports: PREREQUISITE NOT MET(45 packages' built type entries absent in this worktree). Its own header states this is neither a pass nor a finding. It is outside the derived family for this change set, and this diff touches no README and no package.check-cross-package-test-inputs --union-into "$RUNNER_TEMP/…",check-shard-attestation --shard ${{ matrix.shard }},check-test-completeness "$RUNNER_TEMP/…".No changeset:
scripts/publishes nothing, so this carriesskip-changeset.Deliberately out of scope
The other 14
unresolvedrows are different shapes and stay reported, per the card's own scope:codeidentifier opaque at the site —code=code status=status,code=code status=422,code=code status=403(core/artifact-packages.ts:147,mcp/stdio-data-bridge.ts:192,metadata-protocol/protocol.ts:2769,metadata-protocol/sys-metadata-repository.ts:1601,plugin-sharing/share-link-service.ts:353,runtime/artifact-collections.ts:184,service-storage/attachment-access-hooks.ts:91).raw.code/res.status,reject.code/reject.status,refusal.code/refusal.status(x2),info.code/info.status,p.code/p.status.\s*=\s*matches the FIRST=of===, soif (typeof e.code === 'string') return e.code;atpackages/create-objectstack/src/detect-package-manager.ts:104is read as an assignment. It is inert today (neither half resolves, so it can only ever be reported, never fabricated into a producer) but it is noise in the census.None is the mechanical shape this card names, and each plausibly has its own reason to stay unresolvable. Not folded in and no row invented for them; they are handed back in the dev report for the PM to file or drop.
🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code