Repository navigation
feat(gates): cover the permission authoring shapes in designer-field-key-parity - #7272
Merged
Merged
Conversation
…key-parity `scripts/check-designer-field-key-parity.mjs` watched one authoring surface: the six object/field designer shapes, judged by `FieldSchema` / `ObjectSchema`. The permission matrix is a second surface with both of the gate's ingredients and was in none of them, so the retired-key class the gate exists to catch was invisible there — objectui#6595 (the retired `allowRestore` / `allowPurge` checkboxes) is an instance of exactly that class, and it reached this repo as a hand-written card from the upstream retirement rather than from CI. Adds `PermissionSetDraft` (the record `PermissionMatrixEditor.doSave` hands to `client.save`) and `ObjectPerm` (one authorization row inside its `objects` map) to `PAYLOAD_SHAPES`, judged by `PermissionSetSchema` and `ObjectPermissionSchema`. Both permission schemas are exported from `@objectstack/spec/security`, not `/data` — measured against the installed 17.2.0, which exports neither on `/data`. So the subpath becomes per-oracle, declared in `ORACLE_SPECIFIERS` (`ORACLES` is now derived from it, so the two cannot drift). `/security` carries the identical dual-package export map, so the header's provenance argument for resolving through a dynamic `import()` applies unchanged, and the self-test pins all four oracles by reference identity. An oracle the map does not carry is an `ExtractionError`, never a silent default to `/data`. The gate stays emit-side. `ObjectPerm` is a hand-written subset of the spec's `ObjectPermission` (objectui#6605); surfacing that under-coverage is a separate card, and a test asserts the gate stays silent about the omitted keys so a charter change has to argue for itself. Self-test: the per-shape non-vacuity guard was a literal `label` on every shape, which an authorization row does not have. It is now a witness key per shape, and every entry in `PAYLOAD_SHAPES` must appear in that table, so a new shape cannot land unwitnessed. The ledger's oracle validation reads `ORACLES` rather than re-listing it. No behaviour change outside the gate; no package source touched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
This was referenced Sep 2, 2026
yinlianghui
marked this pull request as ready for review
September 2, 2026 02:59
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6606
scripts/check-designer-field-key-parity.mjswatched exactly one authoringsurface. Its six
PAYLOAD_SHAPESare all the object/field designer path, judgedby
FieldSchema/ObjectSchema. The permission matrix is a second surfacecarrying both of the gate's ingredients — statically declared payload shapes,
and strict zod schemas that refuse unknown keys by name — and it was in none of
them, so the retired-key class the gate exists to catch was invisible there.
objectui#6595 (the retired
allowRestore/allowPurgecheckboxes) is aninstance of precisely that class, and it reached this repo as a hand-written
card from the upstream retirement (objectstack#12497) rather than from CI.
What this adds
Two entries in
PAYLOAD_SHAPES, both inpackages/app-shell/src/views/metadata-admin/permission-slice.ts:PermissionSetDraftPermissionSetSchema@objectstack/spec/securityObjectPermObjectPermissionSchema@objectstack/spec/securityPermissionSetDraftis the recordPermissionMatrixEditor.doSavehands toclient.save(type, name, toSave);ObjectPermis one authorization row insidethat record's
objectsmap, so it reaches the same saved body one level down —the field-in-object nesting again, which is why it needs its own oracle rather
than riding on the record's.
The subpath is per-oracle now, and that was a measurement
The permission schemas are not on
/data. Measured against the installed@objectstack/spec17.2.0:/dataexports neitherObjectPermissionSchemanorPermissionSetSchema;/securityexports both. So the hardcodedimport("@objectstack/spec/data")becomes a declared map,ORACLE_SPECIFIERS,and
ORACLESis derived from it (Object.keys) so the two cannot drift.The two header constraints are kept, not worked around:
carry throws
ExtractionErrorrather than defaulting to/data— a silentdefault there would resolve the wrong module (or nothing) and produce the
confident green the file exists to prevent. There is no silent-skip path on
either new entry.
file. No permission key list is hardcoded; resolution stays through the
dynamic
import()./securitycarries the identical dual-package export map(
dist/security/index.jsforrequire,dist/security/index.mjsforimport), so the provenance argument the header spells out applies to itunchanged — and the self-test pins all four oracles by reference identity
against a plain static import from the subpath named in the map.
Emit-side, deliberately
ObjectPermis a hand-written subset of the spec'sObjectPermission(itomits
allowExport,readScope,writeScope— objectui#6605). Surfacing thatunder-coverage stays a separate card, so this is the same emit-side check as
every other entry. A test asserts the gate stays silent about the omitted keys,
so widening the charter has to argue for itself rather than happen by drift.
Gate verdict lines
Before (on
67dadd6, two oracles, six shapes):After (
18c3f61, four oracles, eight shapes):Green on today's tree, as the filing card predicted it would be once
objectui#6595 had landed: every key both shapes declare is accepted by the
schema that judges it.
Teeth, proven by ablation
The card is fenced against editing
permission-slice.ts, so the mutation wasapplied to a copy of that file in a throwaway root and the real
PAYLOAD_SHAPESentries — realinterface, realschema, realreach— wererun against it. The thing under test is the entry that ships, not a fixture
look-alike.
Both legs prove they landed on disk by counting the injected and the original
text, not by reading an editor's exit code, and the fenced file is shown
byte-identical to its
HEADblob before and after.Self-test
The per-shape non-vacuity guard was a literal
labelon every shape. Anauthorization row does not have one, so that assertion is now a witness key per
shape (
ObjectPerm->allowRead,PermissionSetDraft->objects), withevery entry in
PAYLOAD_SHAPESrequired to appear in the table — a shape cannotland unwitnessed and read as verified. The ledger's oracle validation now reads
ORACLESinstead of re-listing['FieldSchema', 'ObjectSchema'], so adding anoracle cannot leave it behind.
Thirteen new cases (
it()count in this file goes 32 -> 45, and 45 + 7 inzod-wrapper-keys.shared.test.tsis exactly the 52 vitest reports as passing, sonone of them was skipped): reference identity for both permission oracles; the
/data-exports-neither measurement asserted rather than trusted; both accept setsreal and different; strictness with a parsing control body; a realistic negative
control (
readable, aFieldPermfacet, on an object-permission row); per-shapeoracle routing (
objectslegal on the record, refused on a row); theunresolvable-schema throw; the unmapped-oracle throw; both real entries present
and wire-bound; the index signature on
PermissionSetDraft; every emitted keyaccepted on the real tree; and the emit-side charter assertion.
Verification
pnpm check:designer-field-key-parity(before,67dadd6)designer-field-key-parity: OKpnpm check:designer-field-key-parity(after,18c3f61)designer-field-key-parity: OKpnpm exec vitest run scripts/__tests__/check-designer-field-key-parity.test.ts scripts/__tests__/zod-wrapper-keys.shared.test.ts --maxWorkers=2Test Files 2 passed (2) · Tests 52 passed (52)pnpm type-check:scriptspnpm check:control-bytescheck-control-bytes: OK (scanned 5996 tracked text file(s); skipped 85 binary)pnpm check:entry-guardcheck:entry-guard: 58 scripts/ file(s) — no entry guard outside the baselinepnpm check:esm-specifiersno un-ledgered package emits an extensionless relative specifiernode scripts/check-changeset-presence.mjsNo source or published contract of a released package changed in this range, so no changeset is owed.pnpm check:node-esm-load(the full two-leg form) is not run here and thenarrowing is declared rather than implied: its subject is published packages and
its load leg builds them, which exceeds this container's foreground limit. Its
cheap specifier leg — the one that reads source — ran green above, and the
changeset-presence gate measured the population independently: of the 2 changed
files, 0 are published source of a package the release covers and 0 are a
manifest whose published contract moved. Nothing under
scripts/is a publishedpackage entry, so this diff cannot move any package's emitted specifiers. CI runs
the full farm regardless.
Every row above was run on
18c3f61with a clean working tree (git status --porcelainempty), so the greens describe the tree this PR proposes and not anearlier one. The two test files are the complete set that names this script —
git grep -l check-designer-field-key-parity -- 'scripts/__tests__/*'returnsexactly those two — and
ci.yml:359is the only workflow line that runs thegate itself.
No changeset:
scripts/-only, per the presence gate's own verdict quoted above.No
packages/source touched;permission-slice.tsis untouched, as the cardrequired.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
Generated by Claude Code