feat(types): retire page.assignedProfiles on both faces, in step with the installed spec (objectui#9409) - #11284
Conversation
…th the installed spec (objectui#9409) `@objectstack/spec` 17.5.0 turned `PageSchema.assignedProfiles` into a `retiredKey()` tombstone (ADR-0090 D2 deleted the Profile concept; ADR-0049 enforce-or-remove). objectui still declared it as an authorable `string[]` described as "Profiles that can access this page", which nothing enforced. - zod: drop the `string[]` override so the spec's tombstone reaches `PageNodeSchema` by reference through `SpecPageFields`. - TS: drop `assignedProfiles` from the `Omit` list and the hand-written member, so the twin takes the spec's member by reference; rewrite the forward-compat docblock as the retirement. - Pin: invert the twins-spec-by-reference pin into a refusal pin on both faces. - Docs: mark the schema-reference row retired. - Changeset (minor, breaking authoring) plus a dated note on the pending objectui#9736 changeset whose `assignedProfiles` sentences this makes false. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…d `assignedProfiles` row (objectui#9409) `doc-version-claims.test.ts` refuses a version literal on a doc surface that nothing in this repository re-verifies. The row now says `@objectstack/spec` retired the key, with no version number; the rest of the row is unchanged. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs, and nothing else: card #9409 (body and all 10 comments, rulings ① Derived judgmentsEvery accept-set and public-surface change the diff implies, named right or wrong:
② Semver level
③ Boundary flagsRound 1
Round 2
Fences from triage and the claim, all held: Check-runs on the head, read as the last input of this act: 42 in all; 35 success, 3 skipped (the path-filtered Implemented-by: VERDICT: PASS Contract review · |
Fixes #9409
Clause-②: no
What this does
@objectstack/spec17.5.0 retiredpage.assignedProfiles: ADR-0090 D2 deleted the Profile concept the key was named after, and under ADR-0049 enforce-or-remove the spec'sPageSchemanow declares the key as aretiredKey()tombstone that refuses any value.@object-ui/typesstill declared it as an authorablestring[], described as "Profiles that can access this page", on both published faces. Nothing in this repository ever read the key, so the key read as access control and enforced nothing.Both faces now take the spec's tombstone by reference, the same way App
versionand DashboardrefreshIntervalalready do:packages/types/src/zod/layout.zod.ts,PageNodeSchema): the localassignedProfiles: z.array(z.string())override is deleted. It shadowed the tombstone thatSpecPageFieldsalready carries in from the spec. An authored value, an empty list included, now fails to parse at pathassignedProfileswith codeinvalid_typeand the spec's own message, which names the remedy (permission sets bound through positions). The describe text "Profiles that can access this page" is gone, as triage ruled it must be under any route.packages/types/src/layout.ts,PageNodeSchema):assignedProfilesis dropped from theOmitoverSpecPage, and the hand-writtenstring[]member is deleted. The twin now inherits the spec's member, whose type admits no value, so authoring one is atscerror. The "FORWARD-COMPAT omission" docblock is rewritten to state the retirement.twins-spec-by-reference-9736.test.ts): the pin "PageassignedProfileskeeps the hand-writtenstring[]" is inverted into a refusal pin inside the existing "spec tombstones surface on the twin as a refusal" group. It asserts that the twin's member equals the spec's and equalsundefined, that the key is still DECLARED (so the@ts-expect-errorcannot be absorbed by the index signature), two@ts-expect-errorauthoring sites, and a parse whose issues are exactly[{ code: 'invalid_type', path: ['assignedProfiles'] }], with a message naming the key. An empty list gets the same refusal. A control case shows the same document without the key, and the admitted spec keys, pass both faces. The stale case "Pagecarries no tombstone on the installed pin" (true on 17.4.0, false on 17.5.0) is replaced by that control.content/docs/api/schema-reference.md): theassignedProfilesrow is rewritten as a retired-key row, following the table's existingbodyprecedent (type*retired*, "Refused by name")..changeset/9409-retire-page-assigned-profiles.mdisminorwith a BREAKING (authoring) banner, per the version policy in AGENTS.md §9. The pending.changeset/9736-twins-spec-by-reference.mdsaid the key is withheld "for forward compatibility" with itsstring[]member unchanged. That is now false, so it gets a dated, append-only note; its frontmatter is byte-identical (md5 of the first three lines is the same before and after).Premises re-measured at
origin/maine420df31before any edit@objectstack/specresolves to 17.5.0. WithPageSchema.safeParsein one run: a fully valid page without the key parses (success=true); the same page withassignedProfiles: ['admin']is refused with exactly one issue,invalid_typeat['assignedProfiles'](expectednever); a never-declared key is refused asunrecognized_keys.name: 'p', type: 'object') fails for three other reasons too (nametoo short,labelmissing,typenot a page kind). Its exit code is right, but its "lit control" is not a green parse. The readings here use a valid page.git grep(lockfile excluded) finds only the declaring sites, the pin, the docs row, and the pending 9736 changeset.skills/**has zero mentions, against a lit control of 19 files there that mention pages. A case-insensitive variant probe also finds nothing. Every non-test use ofPageNodeSchemais a TypeScript type position; nothing walks the zod shape, and the key set is unchanged anyway (the key is still present, now as the tombstone).zod-mirror-parity.test.tsrow names the key: TRUE. That file is not edited.Route: by reference, not a local
retirementTombstone(the suggested route, changed on measurement)The dispatch suggested the repository's retired-key idiom (
retirementTombstoneplus?: never). Measurement points the other way for this key.tombstone.zod.ts's own docblock, under "Not@objectstack/spec'sretiredKey", reserves that helper for keys objectui retires itself and says the two are not to be swapped. For keys the SPEC retires, this package's idiom is to take the spec's tombstone by reference: Appversionand DashboardrefreshIntervalare pinned exactly that way in the same test file. So the refusal message is the spec's own text, with no second copy to drift. The local override was the only thing standing between the node and that tombstone.Tests (all at
59c78cbe5, the head of this PR)pnpm --filter @object-ui/types build, thenpnpm --filter @object-ui/types type-check(tsc --noEmit, the examples project, andtsconfig.test.json): lockVERDICT command-exit 0.tsc -p tsconfig.test.json --listFilesincludes the pin file, so the@ts-expect-errorlines are enforced.pnpm exec vitest run packages/types/(repo-root form): Test Files 298 passed (298), Tests 7465 passed (7465),zod-mirror-parity.test.tsandpage-app-dashboard-spec-parity.test.tsincluded. The pin file on its own: 9 passed, and the new case is listed by name.PageNodeSchema):@object-ui/components(its dependency closure built first) and@object-ui/runner(--filter '@object-ui/runner^...' buildfirst, since it reads built.d.ts).type-checkpasses on both.pnpm --filter @object-ui/types lint: 0 errors. The 300 warnings are allno-explicit-anyin files this PR does not touch.Ablation (committed first, restored through
ablation-replace.mjs, and every restore proven by blob hash plus an emptygit diff HEAD)z.array(z.string())override is re-inserted, and the pin file is run. 1 failed, 8 passed: the refusal case fails withexpected true to be false(the mirror parsed the value), and the controls stay green. Restored; the blob equals HEAD.'assignedProfiles'is put back in theOmitlist and thestring[]member is restored (two anchors: hold, then wrap), andtsc -p tsconfig.test.jsonis run. Exit 2, with 4 errors in the pin (bothEqualassertions turn false, and both@ts-expect-errordirectives become unused), pluszod-mirror-parity.test.tsreportinglayout.zod.ts#PageNodeSchemaas new drift between the faces. Restored; the blob equals HEAD.Gates (hand-derived:
dispatch-gates.mjsderives nothing for objectui)Exit 0:
check:spec-symbols,check:doc-types,check:doc-fences,check:doc-example-ids,check:doc-example-readers,check:new-line-citations(0 new citations),check:control-bytes,check:changeset-claims,check:pending-changeset-literals,check:installed-pin-claims(pin 17.5.0, OK),check:component-surface-parity,check:unreferenced-sources,check-changeset-presence.mjs,check-changeset-no-major.mjs, andcheck-governed-queue-guard.mjs --testover the six paths (NOT GOVERNED).NOT MEASURED:
check:doc-snippetsandcheck:doc-examplesboth exited 2 with PRECONDITION NOT MET, because 21 or more packages outside this diff's closure are unbuilt. Neither gate reads anything this diff changed: the onlycontent/docsedit is one table row with no fence lines, and.changeset/is excluded from the snippet corpus by that gate's own rule. CI runs both.check:changeset-claims(report-only) listed 28 pending changesets that name a file this PR touches. I grepped each one's body forPageNodeSchema, profiles,Omit, forward-compat, andstring[]: none is made false by this change. The one that IS made false, 9736, names no file path, and it carries the dated note.Acceptance notes
retiredKey()tombstone is invisible to a presence-only guard", andassignedProfileswas its only instance. I compared all six spec-by-reference mirrors (PageNodeSchema,AppComponentSchema,DashboardComponentSchema,ListViewSchema,DashboardWidgetSchema,NavigationAreaSchema) against their spec schemas: 24 spec tombstones, 0 shadowed at this head. The detector fires on the pre-change shape (it reportsassignedProfiles:array, 1 shadowed, with the old override ablated back in). With zero live instances the class is dormant. The four-axis frame defaults new gates to "no", so I do not recommend a card or a widened guard. The guard was not widened here, per the triage fence.PageNodeSchema's published TypeScript surface. Code that wroteassignedProfilesused to compile and now failstsc. No workspace source writes the key (see H2), and the two workspace importers type-check clean.This PR was produced by a dispatched dev run under the
domain:spec @ objectuiseat, sessionhttps://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT. It stays draft: flipping it ready, the contract review record and enqueueing belong to the seat.Patch round 2, at HEAD
e423cc3ad(written by the seat)Test (shard 8/8)went red on59c78cbe5inscripts/__tests__/doc-version-claims.test.ts. The new retiredassignedProfilesrow incontent/docs/api/schema-reference.mdspelled the spec version ("@objectstack/spec17.5.0 retired the key"), and nothing in the repository re-verifies that literal.e423cc3adDELETES the literal, the ratchet's preferred remedy, with noKNOWN_CLAIMSentry. The row is otherwise byte-identical (+1/−1).59c78cbe5, then exit 0 ate423cc3ad, together with thetwins-spec-by-reference-9736pin (38 tests).@object-ui/typestype-check exit 0.scripts/__tests__file that reads the docs or changeset surfaces (56 files, 2639 tests) passes ate423cc3ad.Generated by Claude Code