Repository navigation
feat(core): pure row-to-definition mappers for the ADR-0131 upgrade ceremony (C7b build-ahead, PR 1 of 2) - #22959
Conversation
…eremony permissionSetDefinitionFromRow and positionDefinitionFromRow turn one sys_permission_set / sys_position row into the definition it carries, beside convertPositionBindingRows. Pure; no caller yet, never at boot. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude <[email protected]>
…ting to the ceremony Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude <[email protected]>
…re-row-definition-mappers
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 27 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e7057d7a693986a4651a5bd5cbbb5831130a914e && git checkout e7057d7a693986a4651a5bd5cbbb5831130a914e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6a40a09ed952f83b1f765d47cd76aee4a41f6672 74b7c06c6b4422f23533aff3a65bf2841a48d13f && git checkout -B drift-repro 6a40a09ed952f83b1f765d47cd76aee4a41f6672 && git merge --no-ff 74b7c06c6b4422f23533aff3a65bf2841a48d13f
node scripts/docs-audit/affected-docs.mjs --json 6a40a09ed952f83b1f765d47cd76aee4a41f6672
|
…code applies them The changeset's permission-set and position bullets now state each rule's condition (label fallback, description, admin scope, facet defaults) and separate reading only definition columns from the schema-key filter; the ceremony is to apply the mappers (not yet built). Comment-only edits in the position mapper's docblock and pin. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude <[email protected]>
|
CI red, not this PR's ·
|
Contract reviewServed-tier: PR #22959 (card #22937, PR 1 of 2) at the head above, read against ① Derived judgmentsSurface added — right. Equivalence with the originals — right, judged by reading the pairs side by side.
Purity and no behaviour change — right. Neither function performs I/O, touches an engine, or mutates its input (the pins freeze the row); the only module state is the memoised key set, a constant. Nothing calls either function: the four names occur nowhere outside the two new modules, their tests, the barrel and the changeset. Neither runs at boot. No existing export changes shape. No validation, no name judgement — right, as the docblocks say. Neither function calls No dependency added; lean closure held — right.
Changeset, sentence by sentence against the code. Frontmatter
Net: no sentence misstates the exported surface or its semver; four sentences are looser than the code, and one of them ("blank") is strictly false for a whitespace-only description. These are wording corrections to one file; the seat may ask for them before arming, and a new head then needs a fresh record on it. ② Semver level
ADR-0087 marker — none owed. The gate judges only a changeset that declares a breaking change; a ③ Boundary flags
Implemented-by: VERDICT: PASS Adopted by |
Contract reviewServed-tier: Delta record for PR #22959 (card #22937, PR 1 of 2). The branch gained exactly one commit since the PASS record on ① Derived judgmentsThe delta changes no code — right. Three files, +5/−5. Each rewritten sentence, read against the code on the head and against the plugin originals on
No sentence the prior record called right became wrong. The prior record's six wording findings (tense, ② Semver levelUnchanged. The changeset frontmatter is still ③ Boundary flagsEvery
Implemented-by: VERDICT: PASS Adopted by |
…re-row-definition-mappers
…re-row-definition-mappers
…row-only position backfill (C3 stage 7b) (#23302) Refs #22975 Clause-②: no C3 (#15204) stage 7b: delete the position write-through and the row-only position backfill. Stage 8 left both unreferenced, so this is dead-code deletion. It is the parallel half of 7a; 8-post follows once both have landed. #22975 stays open. ## What this changes Stage 8 (#23222) stopped registering the `sys_position` row object, the `sys_position` data-door write-through and the row-only position backfill. Their modules stayed in the tree with no caller. This PR deletes them. - **Deleted:** `position-write-through.ts` and `position-environment-backfill.ts`. Measured on `main` before this PR: no file imports `position-environment-backfill`, and only `catalog-reference-report.ts` imports `position-write-through` (for `POSITION_OBJECT` and `metadataDoorAcceptsPositionName`). The backfill is deleted rather than switched to core's `positionDefinitionFromRow`. That mapper (#22959) stays in core for the upgrade ceremony; no boot path calls it. - **`PositionNameConflictError`** goes with the write-through, its only thrower (ruling Q4 → A). It was never exported from the package's entry. - **The catalog reference report** (`catalog-reference-report.ts`) drops categories 4 and 5: - category 4: a position row with no definition; - category 5: a position name that several rows carry. It also drops the `posture` input, which only those two categories read. No composition registers `sys_position` after stage 8, so those categories could never read a row. Legacy position rows are the upgrade ceremony's to convert and report (ADR-0131 D10). The report still lists, per organization: - every assignment whose position the catalog does not resolve; - every grant whose permission set the catalog does not resolve; - every grant that names no permission set. The report is not exported. Seat answer Q1 → A. - **Generated, regenerated with the repo's own tools:** - row 11b leaves `content/docs/permissions/system-context.mdx`; - the write-through's `UNTYPED_RECEIVERS` row leaves `scripts/tenant-audit-census.mjs`; - the tenant-audit census page and counts are regenerated with `node scripts/tenant-audit-census.mjs --write`, and the prose figures the gate names are updated (228 → 225 sites). No export of `@objectstack/plugin-security`'s entry changes. The changeset is a `patch`, so no ADR-0087 marker is needed. ## Acceptance notes - **Overlap with 7a.** 7a edits the same census, system-context and `security-plugin.ts` regions. Whichever PR lands second merges `main`, re-runs `node scripts/tenant-audit-census.mjs --write` and updates the census prose figures. - **Not here:** - the comment sweep and the remaining catalog gates (8-post); - the catalog reference report's sixth class (8-post, director ruling #23229 → B). ## Verification On head `8c7ee7f06a` (`main` `026391d5d8` merged in), unless noted: - `plugin-security` unit suite: 188/188 files, 3,854 passed (45 skipped). Typecheck: exit 0. eslint on the changed files: exit 0. - `check:tenant-audit-census` and `check:system-context-census`: exit 0. - The derived gate sweep (`dispatch-gates --commands`): 114 derived and 114 run, all exit 0; `--ran` reconciles with 0 NOT-MEASURED and 0 UNRUN. This was measured on `05dee10c89`, the head before the last merge of `main`, whose five commits touch none of these files. - The three symbol-anchor sweeps (adr, scripts, spec-docblock): exit 0. - `check:dual-build-cjs-loads` on a full build (74/74 tasks): exit 0. - `check:platform-checklist`: exit 1 with 8 reds. The same 8 reds appear on `main` (they are pre-existing). - Dogfood: 254 files passed (1 skipped), 2,106 tests passed. - CLI integration tier (`--project integration`): 119/119 files, 1,036 tests passed. - objectui pinned sibling: no export of `@objectstack/plugin-security`'s entry changes, so nothing the console imports moves. CI's Console Pin Gate covers it. --- _Generated by [Claude Code](https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS)_ Co-authored-by: Claude <[email protected]>
Part of #22937
Clause-②: yes
Part of #15211
PR 1 of 2 of the C7b build-ahead card: E5's pure row → definition mappers, in core (seat answer 6110777865, E5 → A; its note on #15211, 6110790598). PR 2 (
os migrate organization-ownership --apply) is a separate PR frommainand is not in this diff. No closing keyword here: PR 2 completes #22937.What changes
Two pure functions in
@objectstack/core, besideconvertPositionBindingRowsinpackages/core/src/security/, each in a new file with its own pins, exported fromsecurity/index.ts(and so from the package's.entry):permissionSetDefinitionFromRow(row)(permission-set-row-definition.ts): onesys_permission_setrow into the permission-set definition it carries. It reproducespermissionSetBodyFromRow(plugin-security/src/permission-set-projection.ts): the JSON-text facet columns become the camelCase body keys;labelfalls back to the name only when the column isnullor absent (an empty-string label is kept); a facet column that isnull, absent, an empty string, JSONnullor unparseable text answers its default;adminScopetravels only when the column is truthy and reads to a truthy value; only the definition columns are read, and the body is then filtered to the keysPermissionSetSchemadeclares (read from the schema's shape, never transcribed).positionDefinitionFromRow(row)(position-row-definition.ts): onesys_positionrow into{ name, label, description, delegatable }. It reproduces the mapping the row-only position backfill (position-environment-backfill.ts) applies, which ispositionBodyFromRowinplugin-security/src/position-write-through.ts.PermissionSetRowForDefinitionandPositionRowForDefinition(the columns read, plus an index signature: other columns are accepted and ignored). Both functions return a string-keyed record ofunknownvalues (TypeScript'sRecordofstringtounknown), the type the plugin's copies return, so 7a/7b can switch to them without a cast.Helpers copied into core (new files only):
parseMaybeJsonand the spec-key filter (permissionSpecBodyKeys/pickSpecDeclaredKeys, folded into the function) frompermission-set-projection.ts;positionBodyFromRowis itself the position mapping. Core already depends on@objectstack/specand already imports values from@objectstack/spec/security, so no dependency is added. Nothing inpackages/plugins/plugin-security/**is edited; #15204's 7a/7b switch to these functions and delete their copies.No behaviour changes. Nothing calls the new functions yet, and neither runs at boot.
Equivalence with the plugin-security originals (one-time measurement)
Core's tests cannot import plugin-security without a new dependency (core is plugin-security's dependency), so the permanent pins assert the documented outputs on hand-written fixtures, and byte-for-behaviour equivalence was measured once with a scratch script (not committed). It imports the originals and the core functions from source with
tsx, and compares them over a generated population of rows withutil.isDeepStrictEqual(which tells a missing key from anundefinedone), plus key order andJSON.stringify, plus thrown errors:admin_scope; every facet column over 20 values × 20 bases; 50,000 seeded random rows over every column at once, with state columns;null,undefined,{}; the test's own fixture). 0 differing.delegatable, each with and without the row-state columns; the test's own fixture). 0 differing.delegatable: 'yes'reading true) reports 7,174 and 1,152 differing rows, so the comparison can fail.Run at head
e975c410bc(and once before, ate5c8727332), same counts. The final headd477e5fbefis a wording-only commit: changeset text, one docblock line and one test comment, so no code line differs frome975c410bc.Pins
permission-set-row-definition.test.ts(12 tests): full stored row → exact body in key order; every emitted key is declared onPermissionSetSchemaand none is filtered away (a spec rename reddens this); a well-formed row's body passesPermissionSetSchema.safeParse; no state, provenance or identity column travels; already-parsed facets kept; the defaults for absent,null, empty, JSON-null and unparseable facets; fresh defaults per call; the label and description rules; theadminScoperule; anull/undefinedrow; purity (frozen row, same answer twice).position-row-definition.test.ts(9 tests): the four keys in order; every key declared onPositionSchemaand a well-formed body passes itssafeParse; no row-state column travels; the label, description anddelegatablerules; two rows of one name serialize equal exactly when their definitions agree (the backfill's agreement check reads that); the name is not judged; purity.Ablation (committed state, restored and proven)
Both legs through
scripts/ablation-replace.mjs(anchor hit 1 → 0, replacement 0 → 1, blob changed), inside a script with an absolute-pathtraprestore; the subject is imported by relative path, so nodist/is involved.permissionSetDefinitionFromRow:label: row?.label ?? row?.name,→label: row?.label,. 2 of 12 fail (the label rule; the defaults row, whose expected label is the name). Restored: blobf4e53ca6a2cbequals HEAD,git diff HEADempty.positionDefinitionFromRow:row.delegatable === '1'→row.delegatable === 'one'. 1 of 9 fails (thedelegatablerule). Restored: blobb998eca94d1aequals HEAD,git diff HEADempty.Direction observed: red, as expected.
Tests and gates (head
e975c410bc; the final headd477e5fbefchanges wording only)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackate975c410bc(merge base51b43a8dc, 6 paths, +483/-0) derives 63 commands, the same 63 derived at dispatch on1bd612bd68. All 63 were run ate975c410bc, each exit code captured before any pipe: 63 x exit 0.--ranreconciliation: 63 derived, 63 run, 0 NOT-MEASURED (derived from the recorded codes), 0 UNRUN. Readings of note:check:dts-closure72 built packages swept, 171/171 declaration files present;check:dual-build-cjs-loads107 require entry points across 66 packages load (core's two declared-unreachable declaration splits are the existing ledger entries);check:lean-entry-closure@objectstack/objectql/coreadmitted set held exactly (15 packages, both conditions);check:type-check-debt26 raw errors, none above record;check:published-files,check:test-source-alias,check:cross-package-test-inputs,check:issue-citations,check:doc-authoring,check:nul-bytesgreen.pnpm --filter @objectstack/core testate975c410bc: 96 files, 2,402 tests passed. Both new files are in thelocalproject's population (vitest list --project localnames them).pnpm --filter @objectstack/core typecheckate975c410bc: exit 0. The test layer's ledger holds at 4 files / 4 errors, none in the new files, whichtsconfig.test.json's program includes (--listFiles).pnpm turbo run build --filter=@objectstack/objectql(core and its closure included), 14 tasks. Core's CJS and ESM builds both export the two functions, and both.d.tsfiles declare them.--no-inline-config --format json: 5 files, 0 errors, 0 warnings. Population: all 5 sit insideeslint.config.mjs(an ignored file reports as a warning; none did). Invariance: the config never enables type-aware linting (noparserOptions.project, no typed rules), and its four local plugins are per-file AST rules whose only file read is the query-options baseline JSON, which this diff does not touch, so no untouched file's verdict can move. The fullpnpm lintis CI's.Acceptance notes
metadataDoorAcceptsPositionName) and "the rows of one name must agree". Both decide what to write, which PR 2's--applydecides per name. The core mapping's determinism, key order included, is pinned so that comparing two rows' serialized definitions stays meaningful. When 7b deletesposition-write-through.ts, the name predicate goes with it. PR 2 can rely on the door's own refusal (saveMetaItemenforces the item-name grammar andPositionSchema.name), or move the predicate into core at that point. Carrier: PR 2 of C7b build-ahead:os migrate organization-ownership --applyand E5's pure row→definition mappers in core (drafts from main; the --apply PR lands after #15204 stage 8) #22937, and the 7b stage of refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204.@objectstack/runtime(src/index.ts) and@objectstack/plugin-hono-server(src/adapter.ts) runexport * from '@objectstack/core', so they re-export the two functions too.git grepfinds the four new names only in core, so nothing collides.singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204's S8 branch (claude/issue-15204-s8-catalog-objects) deletespackages/core/src/security/row-active.tsand removes its export block fromsecurity/index.ts. This PR adds its block afterconvertPositionBindingRows, a different hunk, and neither new file importsrow-active.ts.Clause-②: yes: two exports widen@objectstack/core's published.entry, so the changeset isminorand one contract-tier review is owed before the queue.Generated by Claude Code