Found while implementing objectui#6551. Filed unassigned, recording only — not graded, no type.
Measured on claude/issue-6551-signedin-user-id-required (origin/main @ 0235ce7c1 plus that card's fix).
What was measured
objectui#6551 narrowed the cast buildExpressionUser reads its input through, so the module now DECLARES what a signed-in session is: id, name and email required, mirroring the spec's AuthUser. But the parameter it narrows behind is still unknown:
exportfunctionbuildExpressionUser(user: unknown): Record<string,unknown>{constu=userasExpressionUserSession|null|undefined;So the declaration binds nothing at any call site. buildExpressionUser({ name: 'B', email: 'b@c.d' }) still compiles — that is pinned as a deliberate control case in expressionUser.sessionContract.types.test.ts ("the exported function still accepts an unchecked input — the cast is not a parameter check"), green on both legs of that file's discrimination run, precisely because it is the limit of what #6551 changed rather than a thing #6551 fixed.
Declared, not enforced. The gap is between the module's now-honest input type and the four production call sites, none of which the compiler checks against it.
The census, as measured
Every production caller passes useAuth().user, whose type is @object-ui/auth's AuthUser (extends the spec's AuthUser, so id/email/name are all required):
packages/app-shell/src/console/AppContent.tsx:659 and :909packages/app-shell/src/views/RecordFormPage.tsx:187apps/console/src/components/InternalFormRoute.tsx:78
So all four would type cleanly against a narrowed parameter today — this is a latent enforcement gap, not a live break, same class as objectui#6551 itself.
Why this is recorded and not fixed
objectui#6551's dispatch fenced it explicitly: narrowing the CAST was the settled shape, and "widening the parameter is a different question and a different card". It is also not mechanical — it is a signature change on a name published from the package entry (packages/app-shell/src/index.ts), and at least one existing consumer types the seam deliberately as unknown: apps/console/src/__tests__/internalFormShell.test.tsx:95 mocks it as buildExpressionUser: (user: unknown) => user.
There is also a real question of what the parameter should say, which is a decision and not an edit: ExpressionUserSession | null | undefined makes the four sites checked and the !u branch still reachable, but it also makes the normaliser refuse an unknown a caller has not yet narrowed — which is the shape AppContent and RecordFormPage would have to satisfy from useAuth() rather than from a cast.
What holds the line today
expressionUser.mountSites.ratchet.test.ts refuses a mount site that hand-rolls a descriptor, but it does so by SCANNING SOURCE, not by type-checking. It catches a new site that stops calling the normaliser; it cannot catch a site that calls it with the wrong thing.
Related
- objectui#6551 — narrowed the cast; this is the residue its fix made visible, and its dispatch named it as a separate card.
- objectui#6534 / objectui#5424 — the shape precedents on this same object.
Generated by Claude Code
Generated by Claude Code
Found while implementing objectui#6551. Filed unassigned, recording only — not graded, no type.
Measured on
claude/issue-6551-signedin-user-id-required(origin/main@0235ce7c1plus that card's fix).What was measured
objectui#6551 narrowed the cast
buildExpressionUserreads its input through, so the module now DECLARES what a signed-in session is:id,nameandemailrequired, mirroring the spec'sAuthUser. But the parameter it narrows behind is stillunknown:So the declaration binds nothing at any call site.
buildExpressionUser({ name: 'B', email: 'b@c.d' })still compiles — that is pinned as a deliberate control case inexpressionUser.sessionContract.types.test.ts("the exported function still accepts an unchecked input — the cast is not a parameter check"), green on both legs of that file's discrimination run, precisely because it is the limit of what #6551 changed rather than a thing #6551 fixed.Declared, not enforced. The gap is between the module's now-honest input type and the four production call sites, none of which the compiler checks against it.
The census, as measured
Every production caller passes
useAuth().user, whose type is@object-ui/auth'sAuthUser(extends the spec'sAuthUser, soid/email/nameare all required):packages/app-shell/src/console/AppContent.tsx:659and:909packages/app-shell/src/views/RecordFormPage.tsx:187apps/console/src/components/InternalFormRoute.tsx:78So all four would type cleanly against a narrowed parameter today — this is a latent enforcement gap, not a live break, same class as objectui#6551 itself.
Why this is recorded and not fixed
objectui#6551's dispatch fenced it explicitly: narrowing the CAST was the settled shape, and "widening the parameter is a different question and a different card". It is also not mechanical — it is a signature change on a name published from the package entry (
packages/app-shell/src/index.ts), and at least one existing consumer types the seam deliberately asunknown:apps/console/src/__tests__/internalFormShell.test.tsx:95mocks it asbuildExpressionUser: (user: unknown) => user.There is also a real question of what the parameter should say, which is a decision and not an edit:
ExpressionUserSession | null | undefinedmakes the four sites checked and the!ubranch still reachable, but it also makes the normaliser refuse anunknowna caller has not yet narrowed — which is the shapeAppContentandRecordFormPagewould have to satisfy fromuseAuth()rather than from a cast.What holds the line today
expressionUser.mountSites.ratchet.test.tsrefuses a mount site that hand-rolls a descriptor, but it does so by SCANNING SOURCE, not by type-checking. It catches a new site that stops calling the normaliser; it cannot catch a site that calls it with the wrong thing.Related
Generated by Claude Code
Generated by Claude Code