From aa067dad3e97fd18c1f0d0d03990dbf3f69e992d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:27:15 +0000 Subject: [PATCH 1/2] docs(plugin-security): re-anchor the dead tracker citations to the commits and ADRs that decided them 266 comment and docblock sites under packages/plugins/plugin-security/src cited 40 tracker numbers that answer 404. Each now cites the in-repo record that decided what the line describes, and says it in its own words: the ADR-0055 amendment for the chain composition, ADR-0094 D5-R for the conflict ruling it records, and otherwise the commit in this repository's history. Comments only: every file keeps its line count and no code token moves. Test titles, two logger strings and three generated translation headers keep their numbers. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude --- .../bootstrap-declared-capabilities.test.ts | 6 +- ...latform-admin-existing-holder-scan.test.ts | 4 +- ...platform-admin-promotion-selection.test.ts | 4 +- ...p-platform-admin-seeded-provenance.test.ts | 4 +- ...tstrap-platform-admin-walled-owner.test.ts | 16 ++-- .../src/bootstrap-platform-admin.ts | 50 ++++++------ .../src/bootstrap-seed-round-trips.test.ts | 20 ++--- .../src/bootstrap-system-capabilities.test.ts | 16 ++-- .../src/bootstrap-system-capabilities.ts | 40 +++++----- .../src/controlled-by-parent-chain.test.ts | 2 +- ...d-by-parent-detail-write-authority.test.ts | 18 ++--- .../src/default-report-sink.test.ts | 2 +- .../plugins/plugin-security/src/errors.ts | 6 +- .../src/explain-engine.test.ts | 10 +-- .../plugin-security/src/explain-engine.ts | 14 ++-- .../identifier-storage-ceiling-pin.test.ts | 18 ++--- packages/plugins/plugin-security/src/index.ts | 2 +- .../src/insert-check-post-image.test.ts | 6 +- .../src/objects/clone-carryover.test.ts | 8 +- .../objects/default-permission-sets.test.ts | 4 +- .../src/objects/default-permission-sets.ts | 6 +- .../src/objects/rbac-objects.test.ts | 2 +- .../sys-audience-binding-suggestion.object.ts | 2 +- .../src/objects/sys-permission-set.object.ts | 10 +-- .../packaged-permission-set-lock-gate.test.ts | 2 +- .../src/packaged-permission-set-lock-gate.ts | 2 +- .../src/packaged-permission-set-lock.test.ts | 32 ++++---- .../src/packaged-permission-set-lock.ts | 2 +- ...ackaged-permission-set-restore-leg.test.ts | 12 +-- .../src/per-organization-catalog.ts | 2 +- ...mission-set-duplicate-name-refusal.test.ts | 2 +- .../src/permission-set-overlay-discard.ts | 2 +- .../src/permission-set-projection.test.ts | 24 +++--- .../src/permission-set-projection.ts | 26 +++---- .../src/platform-owner-wall-bypass.test.ts | 4 +- .../src/platform-owner-wall-bypass.ts | 6 +- .../src/platform-ownership-policies.ts | 4 +- .../src/plugin-keyed-text-bounds.test.ts | 4 +- .../src/record-share-tenant-wall.test.ts | 2 +- .../rls-accessible-org-ids-plumbing.test.ts | 2 +- .../src/rls-check-membership-staging.test.ts | 4 +- .../plugin-security/src/rls-compiler.ts | 4 +- .../src/rls-pushdown-limits.test.ts | 2 +- .../src/row-write-widener-composition.test.ts | 2 +- .../src/security-plugin.test.ts | 10 +-- .../plugin-security/src/security-plugin.ts | 78 +++++++++---------- .../plugin-security/src/seed-name-lookup.ts | 26 +++---- .../src/seed-write-refusal.test.ts | 2 +- .../src/share-link-tenant-wall.test.ts | 2 +- .../plugin-security/src/tenant-layer.test.ts | 2 +- .../plugin-security/src/translations/index.ts | 2 +- 51 files changed, 266 insertions(+), 266 deletions(-) diff --git a/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts b/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts index 4c26b275896..6380f2bae0b 100644 --- a/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts @@ -60,12 +60,12 @@ function makeQl(declared: any[] = []) { return (v === null ? r[k] == null : r[k] === v); }), ); - // [#11518] `limit` is HONOURED, and a paged read is ordered by `id` + // [commit e1d773eb7] `limit` is HONOURED, and a paged read is ordered by `id` // ascending (#4363's pagination tie-breaker). Both are properties of the // shipped drivers, measured for the sibling double in // `bootstrap-system-capabilities.test.ts`; this one ignored `limit` // entirely, which made the whole class of page-cap defect INEXPRESSIBLE - // here — including #11518's, whose consequence lands on THIS seeder. + // here — including the cap commit e1d773eb7 fixed, whose consequence lands on THIS seeder. if (q?.limit === undefined) return matched; return [...matched] .sort((a, b) => (String(a.id) < String(b.id) ? -1 : String(a.id) > String(b.id) ? 1 : 0)) @@ -434,7 +434,7 @@ describe('unowned-declaration diagnostic (#4967 Part 3)', () => { }); /** - * [#11518] THE CONSEQUENCE THIS SEEDER PAYS FOR A TRUNCATED EXISTENCE PAGE. + * [commit e1d773eb7] THE CONSEQUENCE THIS SEEDER PAYS FOR A TRUNCATED EXISTENCE PAGE. * * This is one of the two callers on `main` that read UNSCOPED (the other is * `permission-set-projection`'s overlay pass), and `seed-name-lookup.ts` capped diff --git a/packages/plugins/plugin-security/src/bootstrap-platform-admin-existing-holder-scan.test.ts b/packages/plugins/plugin-security/src/bootstrap-platform-admin-existing-holder-scan.test.ts index ee8362b74a2..74300e52faf 100644 --- a/packages/plugins/plugin-security/src/bootstrap-platform-admin-existing-holder-scan.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-platform-admin-existing-holder-scan.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * #16861 — whether this deployment ALREADY has a platform admin, and why the + * Commit 1c83ca226 — whether this deployment ALREADY has a platform admin, and why the * answer stopped being a function of how many ORG admins it has. * * ## The defect, re-measured on this branch's base before anything changed @@ -58,7 +58,7 @@ * ## Why the row ORDER is permuted rather than a second driver package * * Same reason as `bootstrap-platform-admin-promotion-selection.test.ts` - * (#16682): `@objectstack/driver-memory` cannot be declared here without a + * (commit 9b9581b11): `@objectstack/driver-memory` cannot be declared here without a * `scripts/driver-memory-census.ledger.json` disposition, which is a * maintainer ruling. Each case runs the REAL engine over the REAL * better-sqlite3 driver behind a facade that permutes a result ONLY when the diff --git a/packages/plugins/plugin-security/src/bootstrap-platform-admin-promotion-selection.test.ts b/packages/plugins/plugin-security/src/bootstrap-platform-admin-promotion-selection.test.ts index a64c25c38c0..2c9667b2efb 100644 --- a/packages/plugins/plugin-security/src/bootstrap-platform-admin-promotion-selection.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-platform-admin-promotion-selection.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * #16682 — WHICH user the `single`-posture bootstrap promotes, and why. + * Commit 9b9581b11 — WHICH user the `single`-posture bootstrap promotes, and why. * * ## The defect, re-measured on this branch's base before anything changed * @@ -199,7 +199,7 @@ async function seedUser( email: string, createdAt: string, withAccount: boolean, - // [#16682, maintainer ruling batch #100] Absent means UNVERIFIED, which is + // [commit 9b9581b11, maintainer ruling batch #100] Absent means UNVERIFIED, which is // what `isEmailVerifiedUserRow` reads an absent column as — so every fixture // that does not say otherwise is a row the declared-owner leg must REFUSE. emailVerified = false, diff --git a/packages/plugins/plugin-security/src/bootstrap-platform-admin-seeded-provenance.test.ts b/packages/plugins/plugin-security/src/bootstrap-platform-admin-seeded-provenance.test.ts index f8cff5921f3..661a06ee977 100644 --- a/packages/plugins/plugin-security/src/bootstrap-platform-admin-seeded-provenance.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-platform-admin-seeded-provenance.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * #8692 — what provenance a REAL `bootstrapPlatformAdmin` run leaves on the + * [commit 712e185db] What provenance a REAL `bootstrapPlatformAdmin` run leaves on the * platform default permission sets, and what `os meta resync` then does with it. * * ## Why this file exists at all @@ -136,7 +136,7 @@ async function rowViaEngine(engine: ObjectQL, name: string): Promise { } /** - * A row exactly as a PRE-#8692 install holds it — written the way the old + * A row exactly as an install before commit 712e185db holds it — written the way the old * seeder wrote it, which is to say WITHOUT `managed_by`, so the value comes * from the declaration's `defaultValue: 'admin'` by the very mechanism that * produced it on every install created before the ruling. diff --git a/packages/plugins/plugin-security/src/bootstrap-platform-admin-walled-owner.test.ts b/packages/plugins/plugin-security/src/bootstrap-platform-admin-walled-owner.test.ts index c8067657d39..4c2b9c13e92 100644 --- a/packages/plugins/plugin-security/src/bootstrap-platform-admin-walled-owner.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-platform-admin-walled-owner.test.ts @@ -6,7 +6,7 @@ * History of this surface, because the pins below flip an older family: * - #11184 (framework leg of cloud#1509): walled postures stopped promoting * the first registrant; only the env-declared owner elevated. - * - #11343: the walled match additionally required a VERIFIED email. + * - commit c0714eb5d: the walled match additionally required a VERIFIED email. * - #13147: `OS_PLATFORM_OWNER_EMAIL` became a comma-separated list through * the ONE parser in `@objectstack/core`. * - **#11974 (#11663 L4, maintainer acceptance 2026-08-25, Choice 4A/5A): @@ -24,7 +24,7 @@ * state, and `single` still PROMOTES (Choice 4A — the over-denial guard: * retiring the walled write must not retire the `single` one). * - * - **#16682: the `single` SELECTION is repaired.** That guard used to be + * - **Commit 9b9581b11: the `single` SELECTION is repaired.** That guard used to be * written as "byte-for-byte", and one case snapshotted the incumbent's * refusal to read `OS_PLATFORM_OWNER_EMAIL` on this branch. The incumbent * was the defect: an unordered, cap-50 `sys_user` read sorted client-side, @@ -460,7 +460,7 @@ describe('single posture — "first user is owner" is ruled reasonable and UNCHA }); /** - * ⚠️ RE-AUTHORED by #16682. This case used to assert the opposite — + * ⚠️ RE-AUTHORED by commit 9b9581b11. This case used to assert the opposite — * "never consults the owner-email variable: a declared owner does NOT * redirect the single-org promotion" — and it is worth being explicit about * what changed and what did NOT, because the two are easy to confuse. @@ -479,7 +479,7 @@ describe('single posture — "first user is owner" is ruled reasonable and UNCHA * only on the walled branch. * * The authority for the reversal is a MAINTAINER ruling — 2026-09-08, - * decision batch #100, recorded on #16682 (comment 5587754690), which + * decision batch #100, applied by commit 9b9581b11, which * supersedes the Choice 4A sentence for this one point and states what * survives it, verbatim: * @@ -490,7 +490,7 @@ describe('single posture — "first user is owner" is ruled reasonable and UNCHA * > `single` one, and the over-denial invariant (`adminPromoted === true` * > with a grant row minted) stays pinned. * - * ⛔ An earlier revision of this comment quoted the #16682 TRIAGE seat's + * ⛔ An earlier revision of this comment quoted the TRIAGE seat's * ruling instead. That quotation was the reviewer's F3 finding: a pin * recorded under a maintainer ruling cannot be rewritten under a seat's. * The quotation above is the record that resolved it. @@ -518,7 +518,7 @@ describe('single posture — "first user is owner" is ruled reasonable and UNCHA // #11974's over-denial guard, unchanged: the `single` write still happens. expect(r.adminPromoted).toBe(true); expect(ql.grants()).toHaveLength(1); - // #16682: and it goes to the address the operator declared, not to + // Commit 9b9581b11: and it goes to the address the operator declared, not to // whichever row the driver handed back first. expect(ql.grants()[0]?.user_id).toBe('u_second'); expect(r.basis).toBe('declared-owner'); @@ -550,8 +550,8 @@ describe('single posture — "first user is owner" is ruled reasonable and UNCHA }); // ─────────────────────────────────────────────────────────────────────────── -// [#11974, amended by #16682] The bootstrap-replay trigger set. #11974 -// narrowed it to `single` + create/insert: the #11343 update arm (email / +// [#11974, amended by commit 9b9581b11] The bootstrap-replay trigger set. #11974 +// narrowed it to `single` + create/insert: commit c0714eb5d's update arm (email / // email_verified) fired for the walled verify-then-elevate sequence, which no // longer exists, and its own rationale was that "`single` promotes the oldest // authenticable human and never reads `email`/`email_verified`". diff --git a/packages/plugins/plugin-security/src/bootstrap-platform-admin.ts b/packages/plugins/plugin-security/src/bootstrap-platform-admin.ts index 99f9fdc6cb9..6f8b98a0a43 100644 --- a/packages/plugins/plugin-security/src/bootstrap-platform-admin.ts +++ b/packages/plugins/plugin-security/src/bootstrap-platform-admin.ts @@ -48,7 +48,7 @@ * `ensureDefaultOrganization`). Install that plugin to get * multi-tenant bootstrap. * - * ## Provenance of the seeded permission-set rows (#8692, ruled 2026-08-15) + * ## Provenance of the seeded permission-set rows (commit 712e185db, ruled 2026-08-15) * * The seed insert stamps `managed_by: 'platform'` **explicitly**, so a fresh * install's default sets are platform-owned and `os meta resync` reconciles @@ -60,7 +60,7 @@ * ⚠️ **Installs created BEFORE that ruling carry `'admin'` on these rows.** * The pre-ruling insert omitted `managed_by` altogether, so the value came from * the declared `defaultValue: 'admin'` in `objects/sys-permission-set.object.ts` - * — measured on a real engine (#8804: a seeded row stored `'admin'`, and a real + * — measured on a real engine (commit db923a3a8: a seeded row stored `'admin'`, and a real * resync returned `resynced 0 / resyncSkipped 8`, skipping every shipped * default set). * @@ -263,9 +263,9 @@ interface BootstrapOptions { * silently stale (a changed default set is served with its OLD value until a * `--fresh` wipe). Only platform-owned rows (`managed_by` absent or * `'platform'`) are overwritten. Rows carrying any other provenance are left - * alone: `'user'` / `'admin'` (taken over in Setup — or, on a pre-#8692 - * install, seeded before the platform stamped its own rows) and `'package'` - * (owned by package metadata). + * alone: `'user'` / `'admin'` (taken over in Setup — or, on an install older + * than commit 712e185db, seeded before the platform stamped its own rows) and + * `'package'` (owned by package metadata). */ resync?: boolean; /** @@ -285,7 +285,7 @@ interface BootstrapOptions { const SYSTEM_CTX = { isSystem: true }; /** - * [#16682] The `single`-posture candidate scan's page size and hard ceiling — + * [commit 9b9581b11] The `single`-posture candidate scan's page size and hard ceiling — * what replaced the bare `50` at the promotion read. * * ## The cap's disposition @@ -311,7 +311,7 @@ export const PLATFORM_ADMIN_CANDIDATE_PAGE_SIZE = 200; export const PLATFORM_ADMIN_CANDIDATE_SCAN_CEILING = 5000; /** - * [#16861] The `already_have_admin` guard's page size and hard ceiling — what + * [commit 1c83ca226] The `already_have_admin` guard's page size and hard ceiling — what * replaced the bare, unordered `50` at the HOLDERS read, one read above the * candidate scan. * @@ -438,7 +438,7 @@ function genId(prefix: string): string { * it (the `resolveEngineUpdateDispatch` pattern). * * [#11974 / #11663 L4] NARROWED with the walled elevation's retirement. The - * #11343 `update` arm (payload touching `email_verified` / `email`) existed + * `update` arm of commit c0714eb5d (payload touching `email_verified` / `email`) existed * for exactly one reason: walled elevation was a WRITE that had to be * re-attempted after the owner's verifying update. Under walled postures the * bootstrap no longer writes a grant at all — standing is derived from config @@ -470,7 +470,7 @@ function genId(prefix: string): string { * difference between "the first real sign-up is promoted" and "no platform * admin is ever promoted". * - `single` + `sys_user` update touching `email` / `email_verified`, ONLY - * while an owner address is declared: [#16682, maintainer ruling of + * while an owner address is declared: [commit 9b9581b11, maintainer ruling of * 2026-09-08, decision batch #100] the `single` leg now consults * `OS_PLATFORM_OWNER_EMAIL` and promotes only a VERIFIED holder of a * declared address. So the verifying write is an INPUT to this function's @@ -499,7 +499,7 @@ export function shouldReplayBootstrapFor(opCtx: { const op = opCtx?.operation; if (op === 'create' || op === 'insert') return true; if (op !== 'update' || opCtx.object !== 'sys_user') return false; - // [#16682] The verifying write, and only where it can decide something. + // [commit 9b9581b11] The verifying write, and only where it can decide something. const data = opCtx.data; if (!data || typeof data !== 'object') return false; if (!('email_verified' in data) && !('email' in data)) return false; @@ -515,7 +515,7 @@ export function shouldReplayBootstrapFor(opCtx: { * (`id`, `name`, `active`, `managed_by`, `package_id`) are deliberately NOT * here — resync reconciles the declaration, never the ownership. * - * [#8692] `managed_by` must stay out of this helper even though the seed insert + * [commit 712e185db] `managed_by` must stay out of this helper even though the seed insert * now stamps it. Both paths share these fields, so adding it here would make * every resync RESTAMP the row it reconciles -- silently converting a legacy * `admin`-owned row (which may be a real Setup takeover) into a platform-owned @@ -570,7 +570,7 @@ export async function bootstrapPlatformAdmin( * SAME admin. Before this, the short-circuited pass knew the answer and threw * it away, so the only way to re-own the missed rows was to re-derive the * holder — a second implementation of the two-leg scan above, which is how the - * guard and its copy drift apart (#16861 is what that scan costs to get + * guard and its copy drift apart (commit 1c83ca226 is what that scan costs to get * right). One owner, read by both passes. */ adminUserId?: string; @@ -579,14 +579,14 @@ export async function bootstrapPlatformAdmin( /** [#2705] Existing rows left untouched by `resync` (admin/package-owned). */ resyncSkipped?: number; /** - * [#16682] WHY this target was chosen, when one was. `declared-owner` means + * [commit 9b9581b11] WHY this target was chosen, when one was. `declared-owner` means * `OS_PLATFORM_OWNER_EMAIL` named them; `oldest-authenticable` means nobody * did and the age rule answered. The highest-privilege grant in the system * should not be auditable only by reading which code path ran. */ basis?: 'declared-owner' | 'oldest-authenticable'; /** - * [#16861] How many `admin_full_access` grant rows the `already_have_admin` + * [commit 1c83ca226] How many `admin_full_access` grant rows the `already_have_admin` * guard actually examined before answering. The old read looked at "up to 50, * whichever the driver produced first" and said nothing, so a guard that had * seen the whole population and a guard that had seen a truncated sample of @@ -625,9 +625,9 @@ export async function bootstrapPlatformAdmin( } } else { resyncSkipped += 1; - // [#8692] Neutral by ruling: state the provenance and the action, and + // [commit 712e185db] Neutral by ruling: state the provenance and the action, and // claim NOTHING about intent. This used to say "(intentional - // override)", which is a lie for every row on a pre-#8692 install -- + // override)", which is a lie for every row on a pre-ruling install -- // there the only writer may have been this very seeder one call // earlier, inheriting `defaultValue: 'admin'` rather than any admin // deciding anything. The stored value cannot tell the two apart, so @@ -646,10 +646,10 @@ export async function bootstrapPlatformAdmin( name: ps.name, ...platformOwnedFields(ps), active: true, - // [#8692] Stamp provenance EXPLICITLY rather than letting it fall to the + // [commit 712e185db] Stamp provenance EXPLICITLY rather than letting it fall to the // declaration's `defaultValue: 'admin'`. Without this the platform's own // default sets are stored indistinguishably from admin-authored ones, so - // `os meta resync` skips every single one of them (measured in #8804: + // `os meta resync` skips every single one of them (measured in commit db923a3a8: // resynced 0 / resyncSkipped 8) -- the exact inverse of what #2705 built // the flag for. Matches `bootstrap-builtin-positions.ts` and // `bootstrap-system-capabilities.ts`, which already stamp `'platform'`. @@ -712,7 +712,7 @@ export async function bootstrapPlatformAdmin( return { seeded: seededCount, adminPromoted: false, reason: 'admin_permission_set_missing', ...resyncCounts }; } - // ── Does this deployment ALREADY have a platform admin? (#16861) ────────── + // ── Does this deployment ALREADY have a platform admin? (commit 1c83ca226) ─ // // This read was `tryFind(ql, 'sys_user_permission_set', { permission_set_id: // adminPsId }, 50)` — no `orderBy`, cap 50 — with the predicate that actually @@ -817,7 +817,7 @@ export async function bootstrapPlatformAdmin( } } - // ⛔ The truncation is never silent (#16861). Reaching the ceiling is the one + // ⛔ The truncation is never silent (commit 1c83ca226). Reaching the ceiling is the one // way this scan still answers "no platform admin yet" while one exists, and // that answer does not merely skip a log line — it MINTS A SECOND unscoped // grant and hands it the seeded business records. So it says the number it @@ -1039,7 +1039,7 @@ export async function bootstrapPlatformAdmin( * Mint the grant and RECORD it. One call site for both legs, so the write, * the log and the seed-ownership handoff cannot drift apart per basis. * - * [#16682] The old line was `first user promoted to platform admin: ` + * [commit 9b9581b11] The old line was `first user promoted to platform admin: ` * and nothing else. It is the only record of the highest-privilege grant * this system ever makes, and it did not say WHY that row won or HOW MANY * rows it was chosen from — so a promotion decided by a truncated, @@ -1107,7 +1107,7 @@ export async function bootstrapPlatformAdmin( }; }; - // ── The candidate ORDER, stated to the DRIVER (#16682) ──────────────────── + // ── The candidate ORDER, stated to the DRIVER (commit 9b9581b11) ────────── // // The age rule used to be applied by `[...users].sort(byCreatedAtAsc)` over // whatever `tryFind(ql, 'sys_user', {}, 50)` returned. That read carried no @@ -1146,7 +1146,7 @@ export async function bootstrapPlatformAdmin( // itself into the derivation site (`resolve-authz-context.ts` §6b-config) // and, for the audit answer, `platform-admin-service.ts`. // - // ── Leg 1: the DECLARED owner, when the operator declared one (#16682) ──── + // ── Leg 1: the DECLARED owner, when the operator declared one (commit 9b9581b11) ─ // // `PLATFORM_OWNER_EMAIL_ENV` was imported into this file and read only on // the walled branch. So a deployment that had SAID who the owner is could @@ -1267,7 +1267,7 @@ export async function bootstrapPlatformAdmin( return { seeded: seededCount, adminPromoted: false, - // [#16682, batch #100] The ruling allows either a distinct code or a + // [commit 9b9581b11, batch #100] The ruling allows either a distinct code or a // fold into `declared_owner_not_authenticable` with verification named // in the warning. A distinct code is used for the verification miss so // #14348's refusal keeps its own name and its own pins, and an operator @@ -1319,7 +1319,7 @@ export async function bootstrapPlatformAdmin( '— platform admin NOT promoted. The first human that signs in will be promoted instead; a ' + 'directory row nobody can sign in as would hold a grant it could never exercise.', ); - // ⛔ The truncation is never silent again (#16682). Reaching the ceiling is + // ⛔ The truncation is never silent again (commit 9b9581b11). Reaching the ceiling is // the ONE way an ordered scan can still answer "nobody" while a promotable // human exists, so it says the number it examined instead of letting the // line above read as a statement about the whole table. diff --git a/packages/plugins/plugin-security/src/bootstrap-seed-round-trips.test.ts b/packages/plugins/plugin-security/src/bootstrap-seed-round-trips.test.ts index 1ba445288a1..e8a43fd4ec1 100644 --- a/packages/plugins/plugin-security/src/bootstrap-seed-round-trips.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-seed-round-trips.test.ts @@ -84,7 +84,7 @@ function makeCountingQl( if (key.startsWith('$')) { throw new Error(`counting driver: unsupported combinator ${key}`); } - // [#11451] A `null` comparand is IS NULL, not `=== null`. `driver-sql` + // [commit c33f18592] A `null` comparand is IS NULL, not `=== null`. `driver-sql` // compiles `{ field: null }` to `IS NULL`; a column never written is NULL // in the database and `undefined` in this double, and strict equality // matches neither. Inert for every describe above — none of them issues a @@ -759,13 +759,13 @@ describe('#11096 — a read that CANNOT ANSWER is not the answer "none exist"', }); /** - * [#11451] `bootstrapSystemCapabilities` — the OTHER capability seeder, whose + * [commit c33f18592] `bootstrapSystemCapabilities` — the OTHER capability seeder, whose * definition set is the union of every `systemPermissions[]` string rather than * the explicit `defineCapability` declarations. * * ## What is pinned, and what is deliberately NOT * - * Two halves, and since #11520 BOTH are batched — as two SEPARATE reads asking + * Two halves, and since commit 1a6855226 BOTH are batched — as two SEPARATE reads asking * two different questions, which is the whole subtlety: * * - the CURATED half (`KNOWN_CAPABILITIES`) costs ONE batched `$in` read @@ -779,9 +779,9 @@ describe('#11096 — a read that CANNOT ANSWER is not the answer "none exist"', * and the `platformStampedInOrg` suite in `bootstrap-system-capabilities.test * .ts` are what stop it. * - * ⚠️ These counts MOVED in #11520, deliberately: this doc previously stated the + * ⚠️ These counts MOVED in commit 1a6855226, deliberately: this doc previously stated the * derived residue as `1 + derived` and said "a later card that batches it is - * expected to move these numbers deliberately". #11518 removed the objection + * expected to move these numbers deliberately". Commit e1d773eb7 removed the objection * that kept it per-item (the page cap became a measurement, so an unnarrowed * batched read that truncates degrades loudly instead of silently reading * `absent` and inserting), so the residue is now a second constant read rather @@ -810,7 +810,7 @@ describe('#11451/#11520 — BOTH halves are O(1) round trips, as two differently }; const rows = [await measure(0), await measure(5), await measure(20)]; - // [#11520] FLAT, not `1 + d`. One read for the curated half, one for the + // [commit 1a6855226] FLAT, not `1 + d`. One read for the curated half, one for the // derived half — and at d=0 the derived read is not issued at all, because // `buildExistingByName` returns before reading when no name survives its // filter. That asymmetry is the reason the expectation is written out per @@ -846,7 +846,7 @@ describe('#11451/#11520 — BOTH halves are O(1) round trips, as two differently }); /** - * ⭐ [#11520] The RULED pin on the derived read's SHAPE. `bootstrap-system- + * ⭐ [commit 1a6855226] The RULED pin on the derived read's SHAPE. `bootstrap-system- * capabilities.test.ts` pins the consequences (#8751's `platformStampedInOrg`, * #8552's untouched bucket); this pins the cause, because the cheap fix that * reverses both is a one-key edit right here. @@ -949,7 +949,7 @@ describe('#11451/#11520 — BOTH halves are O(1) round trips, as two differently scope: c.scope, managed_by: 'platform', organization_id: null, active: true, }))); const warns: string[] = []; - // No derived names here: this pin is about the CURATED half, and #11520 adds + // No derived names here: this pin is about the CURATED half, and commit 1a6855226 adds // the derived counterpart as its own test below rather than widening this one. const r = await bootstrapSystemCapabilities(broken, [], { logger: { warn: (m) => warns.push(m) } }); expect(r.unreadable).toBe(KNOWN_CAPABILITIES.length); @@ -961,7 +961,7 @@ describe('#11451/#11520 — BOTH halves are O(1) round trips, as two differently }); /** - * ⭐ [#11520] The derived counterpart — and the one place this card changes + * ⭐ [commit 1a6855226] The derived counterpart — and the one place that commit changes * observable behaviour, pinned so the change is a decision rather than a * side effect. * @@ -975,7 +975,7 @@ describe('#11451/#11520 — BOTH halves are O(1) round trips, as two differently * * AFTER: `unknown` is declined, exactly as the shared oracle's module header * requires of every other caller. Strictly stricter, in the direction #10946 - * chose deliberately for the curated half and #11518 extended to truncation. + * chose deliberately for the curated half and commit e1d773eb7 extended to truncation. */ it('⭐ [#11520] a DERIVED name whose read cannot answer is DECLINED, never blind-inserted', async () => { const DERIVED = 2; diff --git a/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts b/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts index 4dbb44254cc..e1f66feaa6f 100644 --- a/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts @@ -32,7 +32,7 @@ import { buildExistingByName } from './seed-name-lookup.js'; * `organization_id: null` unsatisfiable here while working in production. * 4. **`$in` membership**, because the real engine has it (`security-plugin.ts` * already reads `sys_permission_set` with `{ name: { $in: names } }`) and - * the curated half's batched existence read (#11451) uses it. A double that + * the curated half's batched existence read (commit c33f18592) uses it. A double that * refused it would be pinning the double's limits, not the seeder's * behaviour. Every OTHER value-level operator is still REFUSED rather than * read as a column comparison. @@ -954,7 +954,7 @@ describe('[#8751] a platform-STAMPED row inside an organization is not the platf }); /** - * [#11451] WHY THE #8470 PREDICATE TRAVELS INSIDE THE BATCHED READ. + * [commit c33f18592] WHY THE #8470 PREDICATE TRAVELS INSIDE THE BATCHED READ. * * The filing offered "two batched reads, the curated half post-filtering on * `managed_by`/`organization_id` in memory" as the cheap option. It is not @@ -1015,11 +1015,11 @@ describe('#11451 — the batched curated read must carry its predicate, not filt it('WITHOUT the predicate the page hauls every organization\'s copy — and the wrong row answers', async () => { const ql = fixture(); - // ⚠️ [#11518] THIS TEST USED TO PIN THE OPPOSITE OUTCOME, and the change is + // ⚠️ [commit e1d773eb7] THIS TEST USED TO PIN THE OPPOSITE OUTCOME, and the change is // the repair rather than a weakened assertion. The unscoped page was capped // at one row per requested name, so on this same 10-row fixture the two // highest-id platform rows fell off the 8-row page and their names read as - // `absent` — which routes the caller to its INSERT branch. That was #11518's + // `absent` — which routes the caller to its INSERT branch. That was commit e1d773eb7's // measurement, taken here; the cap is now measured rather than trusted // (`seed-name-lookup.ts`), so the page carries all ten rows and loses nobody. const index = await buildExistingByName(ql, 'sys_capability', CURATED_NAMES); @@ -1027,10 +1027,10 @@ describe('#11451 — the batched curated read must carry its predicate, not filt expect((await index.get(name)).status, name).toBe('present'); } - // What #11518 does NOT repair — and cannot — is WHICH row answers. The + // What commit e1d773eb7 does NOT repair — and cannot — is WHICH row answers. The // unpredicated read still hauls both organizations' copies into the page, // and unscoped the first row is the row, so an organization's authored copy - // answers for the platform's own definition. That is #11451 exactly, and no + // answers for the platform's own definition. That is the defect commit c33f18592 fixed, and no // page budget reaches it: only the predicate does. const shared = await index.get(SHARED); expect((shared as { row: any }).row.id).toBe('aaa_org_jia'); @@ -1070,7 +1070,7 @@ describe('#11451 — the batched curated read must carry its predicate, not filt }); /** - * [#11518] THE UNSCOPED PAGE CAP IS A MEASUREMENT, NOT A PROMISE. + * [commit e1d773eb7] THE UNSCOPED PAGE CAP IS A MEASUREMENT, NOT A PROMISE. * * `readNamePage` used to cap an unscoped page at `names.length`, which is exact * only while one row can exist per name. Since #8461 / ADR-0120 D1 the identity @@ -1145,7 +1145,7 @@ describe('#11518 — a page that could not fit the answer must not report "absen // the tail of the curated list that falls off a `length`-capped page. This // read hard-coded `[6]`/`[7]` while the list had eight entries, and went // red the day the list grew — the count belongs to the list, never to - // prose (this file's own #8919-era rule, applied to indices). + // prose (the rule of the write-door census commit b5378550e created, applied to indices). for (const lost of CURATED_NAMES.slice(-2)) { expect(namesOnThePage.has(lost)).toBe(false); expect(ql.rows.some((r: any) => r.name === lost && r.managed_by === 'platform')).toBe(true); diff --git a/packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts b/packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts index a4a5a2e9e00..d0159ca82f2 100644 --- a/packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts +++ b/packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts @@ -41,7 +41,7 @@ * organizations by construction — which is correct for a seeder, and is exactly * why the predicate has to say which row it means. * - * [#11520] The two batched reads inherit that property rather than re-deriving + * [commit 1a6855226] The two batched reads inherit that property rather than re-deriving * it: `seed-name-lookup.ts` reads under `seedCtx(organizationId)`, and with no * organization threaded that is `{ isSystem: true }` — the same object literal as * `SYSTEM_CTX`. Both batched reads and the surviving `tryFind` bucket reads @@ -170,7 +170,7 @@ * of firing on every authored row, which is the state #4632 declined to alarm * about and which remains counted in `skippedAuthored`. * - * [#11451] ROUND TRIPS. The curated half's existence read is now ONE batched + * [commit c33f18592] ROUND TRIPS. The curated half's existence read is now ONE batched * `$in`, and the reconcile is equality-gated for both halves. The design choice * the filing asked to be made deliberately is recorded here so it is not * re-litigated from the diff: @@ -192,8 +192,8 @@ * question and silently reverses part of the #8552 ruling; batching it * unnarrowed needed an unbounded read. * - * [#11520] The derived half is now batched too — UNNARROWED, on the second of - * those two objections being removed rather than accepted. #11518 turned the + * [commit 1a6855226] The derived half is now batched too — UNNARROWED, on the second of + * those two objections being removed rather than accepted. Commit e1d773eb7 turned the * page cap from a promise into a measurement (`readNamePage` asks for one row * more than its budget and calls the overflow `truncated` = "could not answer", * degrading to the per-item read), so "batching it unnarrowed needs an unbounded @@ -421,7 +421,7 @@ export interface CapabilitySeedResult { */ blockedCurated: number; /** - * [#11451] Rows found ALREADY MATCHING the platform's definition, so no + * [commit c33f18592] Rows found ALREADY MATCHING the platform's definition, so no * `UPDATE` was issued. Reported rather than folded into `updated`, for the * same reason #10946 reports it on the sibling seeders: without it, "wrote * nothing because nothing differed" and "wrote nothing because the writes @@ -429,10 +429,10 @@ export interface CapabilitySeedResult { */ unchanged: number; /** - * [#11451] Definitions left ENTIRELY alone because the existence read could + * [commit c33f18592] Definitions left ENTIRELY alone because the existence read could * not answer — not read as absent, and therefore never inserted. * - * [#11520] Counts BOTH halves since the derived read was batched. It was + * [commit 1a6855226] Counts BOTH halves since the derived read was batched. It was * curated-only while the derived half swallowed a failed read into `[]` and * went on to attempt an insert; that half now declines on `unknown` like every * other caller of the shared oracle, so its unanswerable names land here. One @@ -515,7 +515,7 @@ export async function bootstrapSystemCapabilities( let unchanged = 0; let unreadable = 0; - // [#11451] ONE batched existence read for the CURATED half, hoisted out of the + // [commit c33f18592] ONE batched existence read for the CURATED half, hoisted out of the // loop below — the shape #10946 established and #11096 carried to the seeder // next door. Each curated definition used to cost its own sequential // `SELECT … LIMIT 1`: invisible on a local file database, one separate awaited @@ -534,8 +534,8 @@ export async function bootstrapSystemCapabilities( // .ts`'s own index comment names ("exactly the bucket this key part keeps a // singleton"). // - // [#11451] What this half does NOT do is narrow the derived read — see the - // derived index below, which #11520 batched on the terms #11451 could not. + // [commit c33f18592] What this half does NOT do is narrow the derived read — see the + // derived index below, which commit 1a6855226 batched on terms commit c33f18592 could not. const curatedExisting = await buildExistingByName( ql, 'sys_capability', @@ -545,10 +545,10 @@ export async function bootstrapSystemCapabilities( CURATED_LOOKUP, ); - // [#11520] The DERIVED half, batched — UNNARROWED, which is the only shape - // that preserves what it computes. #11451 filed this rather than taking it, + // [commit 1a6855226] The DERIVED half, batched — UNNARROWED, which is the only shape + // that preserves what it computes. Commit c33f18592 filed this rather than taking it, // and the two objections it recorded resolved in opposite ways: one was - // removed by #11518, the other still stands and still forbids the cheap fix. + // removed by commit e1d773eb7, the other still stands and still forbids the cheap fix. // // ## Why the batched read is the SAME question, not a cheaper one // @@ -571,7 +571,7 @@ export async function bootstrapSystemCapabilities( // (`bootstrap-declared-capabilities.ts`: "an unscoped lookup is EXACTLY the // question the per-item read asked"). // - // ## What #11518 removed + // ## What commit e1d773eb7 removed // // The blocking objection was the PAGE CAP, not the question: this set is // bounded only by the number of organizations, against a page that was capped @@ -579,7 +579,7 @@ export async function bootstrapSystemCapabilities( // `absent`, which INSERTS. `readNamePage` now asks for one row MORE than its // budget and reports the overflow as `truncated` — "could not answer" — // degrading, loudly, to exactly the per-item read this half used to do - // unconditionally. So the unbounded-read trade #11451 declined no longer + // unconditionally. So the unbounded-read trade commit c33f18592 declined no longer // exists: the worst case is the old cost plus a warning naming the budget. // // ## What is still forbidden @@ -605,7 +605,7 @@ export async function bootstrapSystemCapabilities( // question — its own `managed_by` guard below is what keeps it off rows it // does not own (#5876), and narrowing its lookup is the thing #8552/#8751 // forbid rather than an optimisation left undone. - // [#11520] ONE consumption idiom for both halves. They consult DIFFERENT + // [commit 1a6855226] ONE consumption idiom for both halves. They consult DIFFERENT // indexes — the curated one carries the #8470 predicate, the derived one is // deliberately unpredicated — but "what does a lookup answer mean" is one // question with one answer, and a second spelling of it is how the two halves @@ -622,7 +622,7 @@ export async function bootstrapSystemCapabilities( // insert per curated name and then report a `blockedCurated` collision // for each, describing a row nobody ever saw. // - // [#11520] The DERIVED half reaches this branch now too, and there the + // [commit 1a6855226] The DERIVED half reaches this branch now too, and there the // strictness replaces something worse than a phantom diagnostic: its // `tryFind` swallowed a failed read into `[]`, which reads as absent, so // the half went on to attempt an insert. Where the read failed but the @@ -774,7 +774,7 @@ export async function bootstrapSystemCapabilities( // refreshed (#2909 T3). A curated scope change in a new platform version // needs a data migration — recorded in the ADR-0094 addendum. // - // [#11451] …and only where they actually DIFFER. This write used to fire + // [commit c33f18592] …and only where they actually DIFFER. This write used to fire // on every boot for every row the pass owns, storing bytes already there — // one more sequential request per definition on a remote database. The // gate sits AFTER the derived-ownership guard above, so it removes write @@ -810,7 +810,7 @@ export async function bootstrapSystemCapabilities( // records the row anyway rather than making the batched read depend on // an invariant that lives somewhere else. // - // [#11520] …and now the derived half records on ITS index for the same + // [commit 1a6855226] …and now the derived half records on ITS index for the same // reason. `byName` is keyed by name so no name repeats within one pass // in either half; both record anyway, because "the read cannot see rows // this loop just inserted" is a property of the hoist, not of the half. @@ -884,7 +884,7 @@ export async function bootstrapSystemCapabilities( // genuinely absent has not been seeded and a drifted one has not been // reconciled; the next boot with a readable database does both. // - // [#11520] "capabilities", not "curated capabilities": the derived half can + // [commit 1a6855226] "capabilities", not "curated capabilities": the derived half can // land here too now. `total` is the whole definition set for the same // reason — measured against `KNOWN_CAPABILITIES.length` a count that // included derived names could exceed its own total. diff --git a/packages/plugins/plugin-security/src/controlled-by-parent-chain.test.ts b/packages/plugins/plugin-security/src/controlled-by-parent-chain.test.ts index b3e3465010e..b1f1a0efeae 100644 --- a/packages/plugins/plugin-security/src/controlled-by-parent-chain.test.ts +++ b/packages/plugins/plugin-security/src/controlled-by-parent-chain.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// [#11082] `controlled_by_parent` (ADR-0055) must COMPOSE ACROSS A CHAIN. +// [ADR-0055 amendment] `controlled_by_parent` must COMPOSE ACROSS A CHAIN. // // ## What was measured before this suite existed // diff --git a/packages/plugins/plugin-security/src/controlled-by-parent-detail-write-authority.test.ts b/packages/plugins/plugin-security/src/controlled-by-parent-detail-write-authority.test.ts index 987447b7c12..7fc6e10b977 100644 --- a/packages/plugins/plugin-security/src/controlled-by-parent-detail-write-authority.test.ts +++ b/packages/plugins/plugin-security/src/controlled-by-parent-detail-write-authority.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// [#8757] ONE row-write authority for a `controlled_by_parent` detail — and the +// [commit 6feac910b] ONE row-write authority for a `controlled_by_parent` detail — and the // COVERAGE PRECONDITION that licenses removing the other one. // // ## What was measured on 17.0.0 GA (the card) @@ -136,7 +136,7 @@ const ORPHAN_SCHEMA = { }; /** - * [#8865] A MASTER on which record sharing ABSTAINS — `private`, but with no + * [commit 498f4e884] A MASTER on which record sharing ABSTAINS — `private`, but with no * owner field, so `checkEdit` returns `abstain` at its `hasOwnerField` gate * before the `modifyAllRecords` branch is ever reached. * @@ -265,7 +265,7 @@ const ADMIN_SET: PermissionSet = PermissionSetSchema.parse({ }); /** - * [#8865] An APP-AUTHORED write policy on the MASTER, carried by a set that + * [commit 498f4e884] An APP-AUTHORED write policy on the MASTER, carried by a set that * grants nothing else. Nothing the platform ships spells it, so it is the * provenance control: dropping the PLATFORM floor must leave this policy * compiling and refusing exactly as before (ADR-0049 — a declared security @@ -324,7 +324,7 @@ function fixtureRows(): Record { crm_orphan_detail: [ { id: 'orph_admin', status: 'sent', created_by: ADMIN, organization_id: ORG }, ], - // [#8865] The owner-less master, one per principal so each case can be run + // [commit 498f4e884] The owner-less master, one per principal so each case can be run // against a master the principal did NOT create — the only state in which // the floor has anything to say. The pair mirrors the campaign fixture // exactly but for the missing owner column, which is what turns the sharing @@ -342,7 +342,7 @@ function fixtureRows(): Record { } /** - * [#8865] An `edit`-level record share — one of the three wideners the platform + * [commit 498f4e884] An `edit`-level record share — one of the three wideners the platform * declares, and the one that is neither ownership nor a superuser bit. Seeding * it is how a case varies the SHARING VERDICT and nothing else. */ @@ -427,7 +427,7 @@ interface Outcome { */ async function boot(seed: { shares?: Row[] } = {}) { const rows = fixtureRows(); - // [#8865] The ONLY fixture axis a case may vary: which record shares exist. + // [commit 498f4e884] The ONLY fixture axis a case may vary: which record shares exist. // Everything else is fixed, so a verdict that moves moved because the declared // widener moved. if (seed.shares) rows.sys_record_share = seed.shares; @@ -670,13 +670,13 @@ describe('[#8757] §2 the card\'s three measured refusals become the master gate const h = await boot(); const admin = h.ctxFor(ADMIN, 'admin_set'); // The card's sharpest line — `admin_set` updating `mem_mkt`, a child it did - // not create, under a master it does not own. #8757 took the DETAIL's floor + // not create, under a master it does not own. Commit 6feac910b took the DETAIL's floor // off, and this assertion then pinned the REFUSAL that survived one gate // later: the master gate's write-RLS leg (leg 1) still ran // `computeRlsFilter(master, 'update')` with the MASTER's platform ownership // floor standing, while the by-id path dropped it on a sharing `allow`. // That was the #8679 divergence surviving in the sibling leg, filed as - // #8865, and it is what this assertion said until #8865 landed. + // the card commit 498f4e884 closed, and it is what this assertion said until then. // // Maintainer ruling 2026-08-15 (direction 1): leg 1 adopts step 2.7's // composition — `resolveSharingWriteVerdict('update', master, masterId, …)`, @@ -772,7 +772,7 @@ describe('[#8757] §4 NON-REGRESSION — everything the floor is still the only // --------------------------------------------------------------------------- /** - * [#8865] §5 ONE ownership composition, both paths — and the bounds that make + * [commit 498f4e884] §5 ONE ownership composition, both paths — and the bounds that make * the flip above attributable to it. * * ## What this section is for diff --git a/packages/plugins/plugin-security/src/default-report-sink.test.ts b/packages/plugins/plugin-security/src/default-report-sink.test.ts index 2856d9fc779..01e71531fc6 100644 --- a/packages/plugins/plugin-security/src/default-report-sink.test.ts +++ b/packages/plugins/plugin-security/src/default-report-sink.test.ts @@ -35,7 +35,7 @@ * it. `start()` binds `ctx.logger` above both of its early bail-outs (#10706), * so this holds on a degraded boot too. * - * ⚠️ There is no `@ts-expect-error` compile-time pin here, and [#13176] changed + * ⚠️ There is no `@ts-expect-error` compile-time pin here, and [commit a68c61267] changed * the REASON rather than the state. Until then `tsconfig.json`'s `**\/*.test.ts` * exclusion was this package's only word on the subject and no tsc program read * this file at all, so a directive here would have evaluated NEVER — not a weak diff --git a/packages/plugins/plugin-security/src/errors.ts b/packages/plugins/plugin-security/src/errors.ts index 7b0914b1749..69c62eeac34 100644 --- a/packages/plugins/plugin-security/src/errors.ts +++ b/packages/plugins/plugin-security/src/errors.ts @@ -181,8 +181,8 @@ export class DetailRecordNotFoundError extends Error { * (a `master_detail` with no `required`; `required` + `readonly`; * `required` + `system`) — there this gate is the only refusal there is, * and its 422 is what stops an unreadable orphan detail row being minted. - * #8772 *proposes* a publish-time lint that would refuse those shapes, but - * it is open and unruled — nothing refuses them at publish today, so an app + * The 2026-08-16 ruling (commit 8abada3ba) orders a publish-time lint ramp, but + * it has not landed — nothing refuses them at publish today, so an app * can newly declare one and land here (#8959). This is the answer those * shapes get for as long as they stay declarable, not merely until some * legacy app is republished. @@ -375,7 +375,7 @@ export const PERMISSION_SET_NAME_CONFLICT_CODE = 'UNIQUE_VIOLATION'; export const PERMISSION_SET_NAME_CONFLICT_STATUS = 409; /** - * [#19307] The data door's duplicate-name refusal on `sys_permission_set`: + * [commit 8f6d83147] The data door's duplicate-name refusal on `sys_permission_set`: * a set with this machine name already exists in the caller's organization, so * the insert is refused. * diff --git a/packages/plugins/plugin-security/src/explain-engine.test.ts b/packages/plugins/plugin-security/src/explain-engine.test.ts index 60c3a6a153e..2e3e7f5762e 100644 --- a/packages/plugins/plugin-security/src/explain-engine.test.ts +++ b/packages/plugins/plugin-security/src/explain-engine.test.ts @@ -10,7 +10,7 @@ import { RLS_DENY_FILTER } from './rls-compiler'; import { unresolvedPostureRemedy } from './unresolved-posture'; import { assertEngineFindOnePredicate, type EngineFindOneQueryInput } from '@objectstack/metadata-core'; -// [#13176] `ExplainDecision.layers` is `ExplainLayer[]` — the z.INPUT shape +// [commit a68c61267] `ExplainDecision.layers` is `ExplainLayer[]` — the z.INPUT shape // (ADR-0122), in which every `.default([])` member is OPTIONAL before a parse: // a layer's `contributors`, and a record attribution's `rules`. The engine // always populates both, which is why the assertions below reach through `?.` @@ -215,7 +215,7 @@ describe('explainAccess (ADR-0090 D6)', () => { // `contributors` is the z.input type (defaulted, so optional pre-parse) — // normalize rather than dereference. Written when this file was outside // every tsc program and the motive was the TEST_DEBT ledger; it is the - // right shape either way, and [#13176] made it the compiler's business. + // right shape either way, and [commit a68c61267] made it the compiler's business. const dropped = (principal.contributors ?? []).filter((c) => c.state === 'expired' || c.state === 'deactivated'); expect(dropped).toEqual([ { kind: 'permission_set', name: 'quarter_close_admin', via: 'held until 2026-06-01T00:00:00Z — expired', state: 'expired' }, @@ -846,7 +846,7 @@ describe('buildContextForUser', () => { ]); }); - // [#8714 / ADR-0049] The second reason of the shared held-state vocabulary: + // [commit 42b05af89 / ADR-0049] The second reason of the shared held-state vocabulary: // the catalogue row's `active` switch. The resolver drops these rows // fail-closed (#8613); the provenance pass re-reads them so the panel can say // "held — deactivated" instead of answering like the grant never existed. @@ -1114,13 +1114,13 @@ describe('buildContextForUser ↔ resolveUserAuthzGrants parity (#6352)', () => { user_id: 'u2', position: 'hr_specialist' }, { user_id: 'u2', position: 'approver', delegated_from: 'u_boss', valid_until: '2026-07-20T00:00:00Z' }, { user_id: 'u2', position: 'payroll_approver', valid_until: '2026-07-01T00:00:00Z' }, - // [#8714] a held position whose catalogue row is switched off + // [commit 42b05af89] a held position whose catalogue row is switched off { user_id: 'u2', position: 'field_auditor' }, ], sys_position: [{ id: 'pos_fa', name: 'field_auditor', active: false }], sys_user_permission_set: [ { user_id: 'u2', permission_set_id: 'ps2', valid_until: '2026-06-01T00:00:00Z' }, - // [#8714] a held direct grant whose SET's catalogue row is switched off + // [commit 42b05af89] a held direct grant whose SET's catalogue row is switched off { user_id: 'u2', permission_set_id: 'psOff' }, ], sys_permission_set: [ diff --git a/packages/plugins/plugin-security/src/explain-engine.ts b/packages/plugins/plugin-security/src/explain-engine.ts index fe299ae87b2..4301f5975ac 100644 --- a/packages/plugins/plugin-security/src/explain-engine.ts +++ b/packages/plugins/plugin-security/src/explain-engine.ts @@ -382,7 +382,7 @@ function untilOfGrantRow(r: any): string | undefined { } /** - * [#8714] One entry of the shared "held but not resolving, because X" + * [commit 42b05af89] One entry of the shared "held but not resolving, because X" * vocabulary (the internal counterpart of the spec contract's * `contributors[].state`): a grant row the principal HOLDS that the resolver * fail-closed DROPPED, with the closed reason enumeration naming why — @@ -399,7 +399,7 @@ export interface DroppedGrant { } /** - * [#6352 / ADR-0091 D2/D3 / #8714] The explain-ONLY provenance pass: the + * [#6352 / ADR-0091 D2/D3 / commit 42b05af89] The explain-ONLY provenance pass: the * annotations the panel prints that the authorization resolver, correctly, * throws away. * @@ -414,7 +414,7 @@ export interface DroppedGrant { * "held until … — expired" instead of silently omitting a grant the admin * knows they granted. This is "why did access DISAPPEAR", and only a dropped * row can answer it. - * - **deactivated** (#8714) — a row whose CATALOGUE entry + * - **deactivated** (commit 42b05af89) — a row whose CATALOGUE entry * (`sys_permission_set.active` / `sys_position.active`) is switched off * (ADR-0049 / #8613), so the grant stopped resolving for everyone holding * it. Deactivation is an incident-response control with no date on the @@ -472,7 +472,7 @@ async function collectGrantProvenance( droppedGrants.push({ kind: 'position', name: p, state: 'expired', until: untilOfGrantRow(r) }); } } - // [#8714 / ADR-0049] A held position whose `sys_position` catalogue row is + // [commit 42b05af89 / ADR-0049] A held position whose `sys_position` catalogue row is // explicitly deactivated was dropped by the resolver (step 6a) — report it // instead of letting it vanish. A name with no row has no flag to read and // is untouched, matching the resolver. @@ -501,7 +501,7 @@ async function collectGrantProvenance( const activeRows = grantRows.filter((g: any) => isGrantActive(g, nowMs)); const ids = Array.from(new Set([...expiredRows, ...activeRows].map(idOf).filter(Boolean))); if (ids.length > 0) { - // [#8714] The existing by-id `sys_permission_set` read now serves both + // [commit 42b05af89] The existing by-id `sys_permission_set` read now serves both // reasons: names for the expired rows, and the ADR-0049 `active` flag for // the held ones — one read, no second query shape. const sets = await ql.find('sys_permission_set', { where: { id: { $in: ids } }, limit: ids.length, context: SYSTEM_CTX }); @@ -1429,7 +1429,7 @@ export async function explainAccess(deps: ExplainEngineDeps, input: ExplainInput if ((context?.permissions ?? []).includes(name)) return 'direct grant'; return 'resolved'; }; - // [ADR-0091 D2 / ADR-0049 / #8714] Held-but-dropped grant rows (populated by + // [ADR-0091 D2 / ADR-0049 / commit 42b05af89] Held-but-dropped grant rows (populated by // buildContextForUser when explaining by userId): present, but contributing // nothing, each carrying the closed reason enumeration (`expired` | // `deactivated`) — reported so "why did access disappear" is self-answering @@ -1480,7 +1480,7 @@ export async function explainAccess(deps: ExplainEngineDeps, input: ExplainInput : { kind: 'position' as const, name: p }; }), ...setNames.map((n) => ({ kind: 'permission_set' as const, name: n, via: viaOf(n) })), - // [#8714] One shared "held but not resolving, because X" vocabulary for + // [commit 42b05af89] One shared "held but not resolving, because X" vocabulary for // every dropped row — the state member IS the reason, closed enum. ...droppedGrants.map((g) => ({ kind: g.kind, diff --git a/packages/plugins/plugin-security/src/identifier-storage-ceiling-pin.test.ts b/packages/plugins/plugin-security/src/identifier-storage-ceiling-pin.test.ts index ef7b8753944..9518fd8bc0a 100644 --- a/packages/plugins/plugin-security/src/identifier-storage-ceiling-pin.test.ts +++ b/packages/plugins/plugin-security/src/identifier-storage-ceiling-pin.test.ts @@ -10,7 +10,7 @@ import { SysMetadataObject } from '@objectstack/metadata-core'; import { SecurityPlugin } from './security-plugin.js'; /** - * #12144 — the shared identifier schemas ↔ the storage columns that bound them. + * Commit 3a04b0125 — the shared identifier schemas ↔ the storage columns that bound them. * * `packages/spec/src/shared/identifiers.zod.ts` declares the platform's * identifier schemas with a floor and a grammar and **no `.max()`** — on @@ -22,11 +22,11 @@ import { SecurityPlugin } from './security-plugin.js'; * 255 for `sys_metadata.name`. A single shared `.max()` therefore cannot * equal every consumer's enforced ceiling: `.max(100)` would newly refuse * `sys_metadata` names in (100, 255] that are legal stored rows today — - * accept-set narrowing beyond enforced reality, fenced out of #12144's - * dispatch by triage. + * accept-set narrowing beyond enforced reality, fenced out of commit 3a04b0125's + * scope by triage. * * This pin links the two surfaces so they cannot drift apart silently, the - * PR #12143 idiom: the widths are READ off the registration surface + * idiom of commit f64668d3c: the widths are READ off the registration surface * (`SecurityPlugin.init()` → the manifest `register({ objects })` call, and * the `SysMetadataObject` declaration), never restated inside the assertions * that use them. Only the one deliberate value pin restates them, so a width @@ -34,7 +34,7 @@ import { SecurityPlugin } from './security-plugin.js'; * * ## What a red on this file means * - * - The **value pin** red: a storing column's width moved. Re-derive #12144's + * - The **value pin** red: a storing column's width moved. Re-derive commit 3a04b0125's * table before accepting: does the spec schema still accept everything the * column stores? Do the columns now AGREE on one width? If they all agree, * the long-fenced declared-=-enforced `.max()` may finally be derivable — @@ -48,7 +48,7 @@ import { SecurityPlugin } from './security-plugin.js'; * identifier schema at or above the widest storing column. Correct ONLY if * every consuming surface's enforced ceiling equals it — which the value * pin above will already be contradicting while the columns disagree. Prove - * the per-surface measurement (#12144's triage fence spells out the burden) + * the per-surface measurement (the triage fence at the top of this file states the burden) * before touching this pin. */ @@ -141,11 +141,11 @@ describe('shared identifier schemas ↔ the storage columns that bound them (#12 }); it('the enforced ceilings, pinned by value — a width change is a #12144 re-derivation moment', async () => { - // Pinned by VALUE, deliberately (the #12143 idiom's one restatement): + // Pinned by VALUE, deliberately (commit f64668d3c's idiom, its one restatement): // these widths are the ENFORCED ceilings on identifier-class values — // enforcement lives at the write seam (ObjectQL record-validator, // `max_length`), never in the spec schemas, which declare no `.max()`. - // If one of these reds, a column width moved: re-derive #12144 before + // If one of these reds, a column width moved: re-derive commit 3a04b0125's table before // accepting — and if the columns now all AGREE on one width, the // declared-=-enforced `.max()` that triage fenced may finally be // derivable; that is a spec accept-set change to escalate, not a value @@ -182,7 +182,7 @@ describe('shared identifier schemas ↔ the storage columns that bound them (#12 // correct ONLY when every consuming surface's enforced ceiling equals // it (declared = enforced, per surface, measured) — while the storing // columns disagree (see the value pin) no shared `.max()` can be, and - // the change is the narrowing #12144's triage explicitly fenced. + // the change is the narrowing triage fenced out of commit 3a04b0125. // Escalate with the per-surface measurement; do not edit this pin to // absorb the red. const widest = Math.max(...(await measuredColumns()).map((c) => c.width)); diff --git a/packages/plugins/plugin-security/src/index.ts b/packages/plugins/plugin-security/src/index.ts index 9f16157d910..c3220bcd134 100644 --- a/packages/plugins/plugin-security/src/index.ts +++ b/packages/plugins/plugin-security/src/index.ts @@ -201,7 +201,7 @@ export type { PackagedSetVerdict, LayeredProbe, } from './packaged-permission-set-lock.js'; -// [#11843 — maintainer ruling 2026-08-25, option B] The lock's METADATA-door +// [commit 5619aace3 — maintainer ruling 2026-08-25, option B] The lock's METADATA-door // registration: the pre-persistence authoring-gate seam consults the SAME // classifier and throws the SAME error classes as the data door above. export { registerPackagedPermissionSetLockGate } from './packaged-permission-set-lock-gate.js'; diff --git a/packages/plugins/plugin-security/src/insert-check-post-image.test.ts b/packages/plugins/plugin-security/src/insert-check-post-image.test.ts index d87cf203414..bc1fc946c92 100644 --- a/packages/plugins/plugin-security/src/insert-check-post-image.test.ts +++ b/packages/plugins/plugin-security/src/insert-check-post-image.test.ts @@ -1,11 +1,11 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#16608] The write `check` judges THE ROW THAT WILL BE STORED — on `insert` + * [commit a016f08b8] The write `check` judges THE ROW THAT WILL BE STORED — on `insert` * as on `update`. * * ## What was measured, on `origin/main` @ `941232040` (already carrying - * #16607's membership staging, PR #16722) + * the membership staging of commit 1d73d45c1) * * The app stamps a denormalised scoping field in `beforeInsert` — an * organization copied from the parent, read OUTSIDE RLS under `runAs: 'system'` @@ -741,7 +741,7 @@ describe('[#16608] fail-closed — an engine that does not run the installed che // ── the contract review's cells, on the same both-drivers footing ────────── /** - * [contract review of PR #16805, F1 — BLOCKING] **The row the seam judges must + * [contract review of commit a016f08b8, F1 — BLOCKING] **The row the seam judges must * be the row that is stored.** * * The first delivery of this card put the judgement immediately after the diff --git a/packages/plugins/plugin-security/src/objects/clone-carryover.test.ts b/packages/plugins/plugin-security/src/objects/clone-carryover.test.ts index 350efd73ccb..1eb0c143d91 100644 --- a/packages/plugins/plugin-security/src/objects/clone-carryover.test.ts +++ b/packages/plugins/plugin-security/src/objects/clone-carryover.test.ts @@ -1,17 +1,17 @@ -// #11992 — the exemplar half of the #11753 ruling (recommendation A, +// #11992 — the exemplar half of the carry-over ruling (commit 0e4e51b0a, recommendation A, // maintainer 2026-08-25): the five `clone_permission_set` facet params DECLARE // the spec's `carryOver` key, so the clone dialog's JSON facets are copied // verbatim, shown read-only, and never offered as prefilled textareas an admin // could hand-mangle into a clone that grants MORE than its base. // -// ⭐ IDENTITIES, NOT COUNTS (same discipline as the #11703 pins one file over): +// ⭐ IDENTITIES, NOT COUNTS (same discipline as commit 5cb62d88b's pins one file over): // "five params declare it" holds constant while two of them swap. Every facet // is asserted by NAME, and the deliberate non-member (`description` — prose, // not a permission facet) is asserted NOT to carry the key, so the boundary of // the declaration is pinned from both sides. // // The SEND side is deliberately not restated here — that is -// `packaged-permission-set-lock.test.ts`'s clone-payload suite (#11703 pin 6), +// `packaged-permission-set-lock.test.ts`'s clone-payload suite (commit 5cb62d88b's pin 6), // which reads the params list and must stay green under this declaration // precisely because `carryOver` changes what the dialog RENDERS, never what it // SENDS. @@ -19,7 +19,7 @@ import { describe, it, expect } from 'vitest'; import { ActionParamSchema } from '@objectstack/spec/ui'; import { SysPermissionSet } from './sys-permission-set.object.js'; -/** The five JSON-serialized definition facets the clone carries (#11703). */ +/** The five JSON-serialized definition facets the clone carries (commit 5cb62d88b). */ const CARRIED_FACETS = [ 'object_permissions', 'field_permissions', diff --git a/packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts b/packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts index 7d1f917a2c6..538d8717593 100644 --- a/packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts +++ b/packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts @@ -280,7 +280,7 @@ describe('sys_invitation is row-scoped to its addressee (#8095)', () => { }); /** - * [#8839] The `sys_comment` moderation carve-out — again a TRIPWIRE, not the + * [commit c25b2d52a] The `sys_comment` moderation carve-out — again a TRIPWIRE, not the * proof. The proof is over HTTP, in * `packages/qa/dogfood/test/comments-permission-matrix.dogfood.test.ts`, which * boots org-bound and arms itself: an assertion whose expectation and reality @@ -332,7 +332,7 @@ describe('sys_comment delete is moderation-shaped, not ownership-shaped (#8839)' expect(floor.using).toBe('created_by == current_user.id'); expect(floor.positions).toEqual(['org_member']); - // The update limb is deliberately NOT widened: #8839 ruled on delete, which + // The update limb is deliberately NOT widened: commit c25b2d52a's ruling is on delete, which // is the limb it measured. A `sys_comment` update policy appearing here is a // second access-widening riding in on this one's ruling. expect(policiesFor('member_default', 'sys_comment').map((p) => p.operation)).toEqual(['delete']); diff --git a/packages/plugins/plugin-security/src/objects/default-permission-sets.ts b/packages/plugins/plugin-security/src/objects/default-permission-sets.ts index 37920bbfa76..fdd958ede98 100644 --- a/packages/plugins/plugin-security/src/objects/default-permission-sets.ts +++ b/packages/plugins/plugin-security/src/objects/default-permission-sets.ts @@ -1015,7 +1015,7 @@ const baseDefaultPermissionSets: PermissionSet[] = [ using: 'inviter_id == current_user.id', positions: [MEMBERSHIP_ROLE_DELEGATED_ADMIN], }, - // [#8839] COMMENT MODERATION — the one object whose delete authority is + // [commit c25b2d52a] COMMENT MODERATION — the one object whose delete authority is // NOT the `created_by` floor above, because a tighter authority already // owns it and the floor was pre-empting it. // @@ -1035,7 +1035,7 @@ const baseDefaultPermissionSets: PermissionSet[] = [ // editor moderating someone else's comment holds `org_member` and is not // the comment's `created_by`, so the floor answered `PERMISSION_DENIED` // before the moderation rule was ever consulted. Net effect measured in - // #8839: moderation was dead in EVERY org-bound deployment, and the only + // commit c25b2d52a: moderation was dead in EVERY org-bound deployment, and the only // fixture that proved the capability // (`qa/dogfood/test/comments-permission-matrix.dogfood.test.ts` case (d)) // was green solely because it booted org-less — #8023's disarm shape, so @@ -1070,7 +1070,7 @@ const baseDefaultPermissionSets: PermissionSet[] = [ // their collection empty and their behaviour byte-identical. // // `delete` only — not `all`. `update` is the other half of plugin-audit's - // rule, and it is deliberately left under the floor: #8839 ruled on the + // rule, and it is deliberately left under the floor: commit c25b2d52a's ruling is on the // delete limb, which is the one it measured. Widening the edit limb is a // separate decision on the same manual floor; do not fold it in here // because the gate happens to share a code path. diff --git a/packages/plugins/plugin-security/src/objects/rbac-objects.test.ts b/packages/plugins/plugin-security/src/objects/rbac-objects.test.ts index 39d230d5df2..df0a05ff995 100644 --- a/packages/plugins/plugin-security/src/objects/rbac-objects.test.ts +++ b/packages/plugins/plugin-security/src/objects/rbac-objects.test.ts @@ -101,7 +101,7 @@ describe('default permission sets', () => { 'owner_only_writes', 'sys_account_self', 'sys_api_key_self', - // [#8839] The one DELETE-class per-object policy, and the only entry here + // [commit c25b2d52a] The one DELETE-class per-object policy, and the only entry here // that widens rather than narrows. `owner_only_deletes` above is a // parent-blind second implementation of "who may remove this row", and on // `sys_comment` it was answering ahead of plugin-audit's diff --git a/packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts b/packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts index 7c40d0b5a12..8c0a91c4624 100644 --- a/packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts +++ b/packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts @@ -41,7 +41,7 @@ export const SysAudienceBindingSuggestion = ObjectSchema.create({ description: 'UUID of the suggestion row.', }), - // [#11374 route A] Both key columns below declare a bound derived by + // [commit f64668d3c, route A] Both key columns below declare a bound derived by // referenced-column transitivity, and the producer is named per column so // the derivation is vetoable in review rather than taken on trust. The pair // is the object's declared unique key `(package_id, permission_set_name, diff --git a/packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts b/packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts index e726f251629..55c7e41edc2 100644 --- a/packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts +++ b/packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts @@ -92,7 +92,7 @@ export const SysPermissionSet = ObjectSchema.create({ visible: "has(record.drift_status) && record.drift_status == 'overlay_shadow'", }, { - // [#11703] ⭐ THIS PARAMS LIST *IS* THE PAYLOAD. Every definition facet a + // [commit 5cb62d88b] ⭐ THIS PARAMS LIST *IS* THE PAYLOAD. Every definition facet a // clone should carry has to be named below: the action POSTs its param // VALUES to the generic data door, so a column that is not a param is // simply absent from the body — `permissionSetBodyFromRow()` then reads @@ -101,7 +101,7 @@ export const SysPermissionSet = ObjectSchema.create({ // a silent grant loss: the clone is created, the success toast fires, and // the difference is discoverable only by diffing the two records. // - // That was live until #11703 — three of the six facets + // That was live until commit 5cb62d88b — three of the six facets // (`system_permissions`, `row_level_security`, `tab_permissions`) were // never listed, so cloning a set carrying system permissions or RLS // produced a clone with none of them. Fail-closed, and therefore quiet. @@ -128,7 +128,7 @@ export const SysPermissionSet = ObjectSchema.create({ // because putting one on a brand-new set on the admin's behalf is a // privilege decision, not a field copy. It is stated HERE, where the // admin is standing when the clone happens, because an UNEXPLAINED - // omission is the same silent drop #11703 reports, merely ruled. + // omission is the same silent drop commit 5cb62d88b fixed, merely ruled. description: 'Copies this set\'s permissions into a new organization-owned set you can edit. ' + 'Delegated-admin scope is not copied — grant it deliberately on the new set if it needs one.', @@ -142,14 +142,14 @@ export const SysPermissionSet = ObjectSchema.create({ { name: 'name', label: 'New API Name', type: 'text', required: true, helpText: 'snake_case machine name, unique per organization' }, // `description` is prose, not a permission facet: it stays editable // (renaming a clone's description is legitimate), while the five JSON - // facets below are declared `carryOver` — the #11753 ruling's + // facets below are declared `carryOver` — commit 0e4e51b0a's // non-editable carry-over. Copied verbatim, shown read-only, never // offered as a prefilled JSON textarea an admin could hand-mangle into // a clone that grants MORE than its base. { field: 'description', defaultFromRow: true }, { field: 'object_permissions', defaultFromRow: true, carryOver: true }, { field: 'field_permissions', defaultFromRow: true, carryOver: true }, - // [#11703] The three facets the clone silently dropped. Same + // [commit 5cb62d88b] The three facets the clone silently dropped. Same // JSON-string shape as the two above: `permissionSetRowFields()` // writes all five with `JSON.stringify`, and the data door parses all // five back — the accept surface did not move, only what is SENT. diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.test.ts b/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.test.ts index 7aaac1b474d..5aa2f35962b 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.test.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * #11843 — the packaged-permission-set lock answers at the METADATA door. + * Commit 5619aace3 — the packaged-permission-set lock answers at the METADATA door. * * The lock (`packaged-permission-set-lock.ts`) used to have exactly one * enforcement point: the `sys_permission_set` DATA door. The pre-persistence diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.ts b/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.ts index 0dadd6a9044..a6de3ffce58 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.ts @@ -2,7 +2,7 @@ /** * THE METADATA-DOOR REGISTRATION of the packaged-permission-set lock - * (#11843; maintainer ruling 2026-08-25, verbatim: 「11843 同意」 — option B: + * (commit 5619aace3; maintainer ruling 2026-08-25, verbatim: 「11843 同意」 — option B: * keep NARROW, move the lock). * * `packaged-permission-set-lock.ts` refuses a write that targets a diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-lock.test.ts b/packages/plugins/plugin-security/src/packaged-permission-set-lock.test.ts index 3fe6f1bcd05..6693f5cc717 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-lock.test.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-lock.test.ts @@ -30,7 +30,7 @@ * and the package-declared base is untouched by it; * 5. fail-closed on ambiguity: a provenance read that cannot ANSWER refuses, * never accepts; - * 6. ⭐ what the clone ACTION SENDS (#11703) — pin 3 drives the door with a + * 6. ⭐ what the clone ACTION SENDS (commit 5cb62d88b) — pin 3 drives the door with a * hand-written payload, so it could not see that the ACTION ITSELF listed * only two of the row's six definition facets. Pin 6 reads the payload out * of the action definition, so editing that params list is what moves it. @@ -38,7 +38,7 @@ * (Pin 4 — the detection reading for overlays that already exist — lives in its * own suite at the bottom of this file, because it reads rather than writes.) * - * ## ⭐ The fail-open this must not inherit (#11518) + * ## ⭐ The fail-open this must not inherit (repaired by commit e1d773eb7) * * `buildExistingByName`'s UNSCOPED page cap (`limit: names.length`, * `seed-name-lookup.ts`) truncated as soon as one name could carry more than one @@ -47,7 +47,7 @@ * the save this ruling exists to refuse would be accepted. A silent fork * produced by the code written to stop silent forks. * - * #11518 has since repaired the cap itself — an overflowing page is now detected + * Commit e1d773eb7 has since repaired the cap itself — an overflowing page is now detected * and degrades to the per-item read rather than answering — so the controls * below no longer guard against THAT truncation reaching this verdict. They are * kept, and they still pass, because what they actually pin is structural and @@ -62,7 +62,7 @@ * an in-memory array with no page, no cap and no `$in`. Two controls prove the * immunity structurally rather than asserting it: * - * - CONTROL A builds the exact multi-row shape #11518 truncates on, in a + * - CONTROL A builds the exact multi-row shape that truncated before commit e1d773eb7, in a * double whose `find` HONOURS `limit` (the projection suite's double ignores * it, so the trap cannot even be expressed there), shows the truncation is * live, and pins that the refusal still fires; @@ -91,8 +91,8 @@ import { * In-memory ql over `sys_permission_set` + `sys_metadata`. * * ⚠️ `find` HONOURS `limit`. The sibling double in - * `permission-set-projection.test.ts` does not, which is why #11518's - * truncation cannot be reproduced there at all — a page cap that the double + * `permission-set-projection.test.ts` does not, which is why the page-cap + * truncation commit e1d773eb7 repaired cannot be reproduced there — a page cap that the double * ignores is a page cap that no test in that file can ever measure. */ function makeQl(declared: any[] | null = null) { @@ -120,7 +120,7 @@ function makeQl(declared: any[] | null = null) { permRows, metaRows, /** - * Break precisely the read #11518 is about: a name-keyed page over + * Break precisely the read commit e1d773eb7 is about: a name-keyed page over * `sys_permission_set`. Reads by `id` (target resolution) keep working, so * the middleware still reaches the provenance question — which is the only * way to observe what that question answers when the paged table is @@ -138,7 +138,7 @@ function makeQl(declared: any[] | null = null) { const rows = tableFor(object); if (!rows) return []; const hit = rows.filter((r) => matches(r, q?.where)); - // The cap a real driver applies — and the one #11518 turns into a false + // The cap a real driver applies — and the one commit e1d773eb7 stopped turning into a false // "absent". Modelled, not ignored. return typeof q?.limit === 'number' ? hit.slice(0, q.limit) : hit; }, @@ -516,7 +516,7 @@ describe('pin 3 — the clone path yields an org-owned set with no upgrade linka }); // ───────────────────────────────────────────────────────────────────────────── -// PIN 6 — what the CLONE ACTION SENDS: every copied facet, by identity (#11703) +// PIN 6 — what the CLONE ACTION SENDS: every copied facet, by identity (commit 5cb62d88b) // ───────────────────────────────────────────────────────────────────────────── /** @@ -527,7 +527,7 @@ describe('pin 3 — the clone path yields an org-owned set with no upgrade linka * on its `params`, so cloning a set carrying system permissions, row-level * security or tab permissions produced a clone with NONE of them — no error, a * success toast, and the loss discoverable only by diffing the two records - * (#11703). Fail-closed (fewer grants), and therefore quiet. + * (commit 5cb62d88b). Fail-closed (fewer grants), and therefore quiet. * * It matters more since the ruling one commit above this one: the save door now * refuses an in-place edit of a package-declared set AND its refusal names the @@ -597,7 +597,7 @@ const richSet = (over: Record = {}) => ({ showcase_project: { allowRead: true }, }, fields: { 'showcase_project.budget': { readable: true, editable: false } }, - // The three facets #11703 dropped, each non-empty and each named below. + // The three facets dropped before commit 5cb62d88b, each non-empty and each named below. systemPermissions: ['setup.access', 'ops.export_data'], rowLevelSecurity: [ { @@ -690,7 +690,7 @@ describe('pin 6 — the clone action SENDS every facet it copies, and says what expect(body.description).toBe(richSet().description); expect(body.objects, 'object permissions').toEqual(richSet().objects); expect(body.fields, 'field permissions').toEqual(richSet().fields); - // ⭐ The three #11703 dropped. `[]` / `{}` here is the defect itself. + // ⭐ The three dropped before commit 5cb62d88b. `[]` / `{}` here is the defect itself. expect(body.systemPermissions, 'system permissions — [] here IS the #11703 silent drop').toEqual( ['setup.access', 'ops.export_data'], ); @@ -752,7 +752,7 @@ describe('pin 6 — the clone action SENDS every facet it copies, and says what expect(clone.admin_scope ?? null, 'and the column stays empty on the clone').toBeNull(); // ⭐ The exclusion has to READ as a decision to the admin standing in the - // dialog — otherwise it is the same silent drop #11703 reports, merely + // dialog — otherwise it is the same silent drop commit 5cb62d88b fixed, merely // ruled. The dialog's own explanatory line carries it. const description = String(cloneAction().description ?? ''); expect(description, 'the clone dialog states the exclusion').toMatch(/delegated-admin scope/i); @@ -806,7 +806,7 @@ describe('pin 5 — provenance that cannot be DETERMINED refuses the save', () = }); // ───────────────────────────────────────────────────────────────────────────── -// ⭐ CONTROLS — the provenance read cannot inherit #11518's fail-open +// ⭐ CONTROLS — the provenance read cannot inherit the fail-open commit e1d773eb7 repaired // ───────────────────────────────────────────────────────────────────────────── describe('control A — #11518 shape: a name carrying MORE THAN ONE row still refuses', () => { @@ -823,7 +823,7 @@ describe('control A — #11518 shape: a name carrying MORE THAN ONE row still re const names = ['ehr_quality_inspector']; const capped = await ql.find('sys_permission_set', { where: { name: { $in: names } }, - limit: names.length, // ← the UNSCOPED cap seed-name-lookup.ts carried before #11518 + limit: names.length, // ← the UNSCOPED cap seed-name-lookup.ts carried before commit e1d773eb7 }); expect(ql.permRows.filter((r: any) => r.name === 'ehr_quality_inspector')).toHaveLength(2); expect(capped, 'the page is truncated — half the rows for this name are invisible').toHaveLength(1); @@ -850,7 +850,7 @@ describe('control A — #11518 shape: a name carrying MORE THAN ONE row still re describe('control B — the provenance read is not a name-keyed table read at all', () => { it('every NAME-KEYED page read fails, and the verdict is STILL "package-declared"', async () => { - // The structural proof of immunity. #11518 is a defect of a name-keyed + // The structural proof of immunity. Commit e1d773eb7 repaired a defect of a name-keyed // page read over `sys_permission_set`; here every such read is made to // fail outright — the most extreme form of "this read did not answer" — // while the by-id target resolution keeps working so the middleware still diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts b/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts index e0b5607290d..9ce83485fec 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-lock.ts @@ -35,7 +35,7 @@ * `sys_permission_set`. The batched existence oracle * (`seed-name-lookup.ts`'s `buildExistingByName`) capped its UNSCOPED page at * `limit: names.length`, which truncates the moment one name can carry more - * than one row — and a truncated page read as `absent`. #11518 has since + * than one row — and a truncated page read as `absent`. Commit e1d773eb7 has since * repaired that: the page budget is measured (one row more than it will hold is * requested, so overflow is DETECTED) and an overflowing page degrades to the * per-item read instead of answering. ⚠️ That does not make this oracle safe to diff --git a/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts b/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts index f18d0420740..2a805d75a26 100644 --- a/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts +++ b/packages/plugins/plugin-security/src/packaged-permission-set-restore-leg.test.ts @@ -3,7 +3,7 @@ /** * THE RESTORE LEG — the fourth write point of `createPermissionSetWriteThrough` * under the 2026-08-24 "lock the base, clone to customize" ruling: guarded - * since #12020, with the refusal on the DURABILITY channel, plus the + * since commit 9cfc1f7e9, with the refusal on the DURABILITY channel, plus the * reachability fence. * * The lock (`packaged-permission-set-lock.ts`) refuses a save that targets a @@ -41,10 +41,10 @@ * refusal is REPORTED on the durability channel with the lock's own error * — never thrown. * - * This case is the INVERSION #11725's MEASURED RESIDUAL demanded of the - * follow-up (#12020): it used to pin the re-authoring of a packaged body + * This case is the INVERSION commit 1e79aa4f8's MEASURED RESIDUAL demanded of the + * follow-up (commit 9cfc1f7e9): it used to pin the re-authoring of a packaged body * through this leg with no lock consulted. The deliberate red for the - * inversion is recorded on #12020's PR: with the leg's lock consultation + * inversion was measured for commit 9cfc1f7e9: with the leg's lock consultation * removed, this case fails on `saves.length` — so it does not pass with * the lock absent. * @@ -297,8 +297,8 @@ describe('[#11725] the restore leg of the permission-set write-through', () => { }); it('LOCK AT THE RESTORE LEG: the same set through RESTORE keeps the engine un-trash but the re-author is REFUSED — reported on the durability channel, never thrown', async () => { - // ⭐ The INVERSION of #11725's MEASURED RESIDUAL, demanded by that case's - // own comment and delivered by #12020. It used to assert: no refusal, one + // ⭐ The INVERSION of commit 1e79aa4f8's MEASURED RESIDUAL, demanded by that case's + // own comment and delivered by commit 9cfc1f7e9. It used to assert: no refusal, one // save, the packaged body in the overlay store. Now the lock is consulted // before the leg's one guarded write, and every half inverts. // diff --git a/packages/plugins/plugin-security/src/per-organization-catalog.ts b/packages/plugins/plugin-security/src/per-organization-catalog.ts index 43c591cab13..42e65b9cf4f 100644 --- a/packages/plugins/plugin-security/src/per-organization-catalog.ts +++ b/packages/plugins/plugin-security/src/per-organization-catalog.ts @@ -311,7 +311,7 @@ export type OrganizationLessRowOrigin = 'platform-bucket' | 'pre-fix-residue'; * Membership is decided by NAME, not by `managed_by`, because the question the * remedy turns on is "will a re-initialized deployment have this row again?" — * and for these names it will, whatever provenance the current row carries (a - * pre-#8692 install stores `'admin'` on the very same names). + * pre-ruling install (before commit 712e185db) stores `'admin'` on the very same names). * * The pass that emits either warning has ALREADY created the organization's own * copies — both describe rows beside that catalog, never a refusal to seed. diff --git a/packages/plugins/plugin-security/src/permission-set-duplicate-name-refusal.test.ts b/packages/plugins/plugin-security/src/permission-set-duplicate-name-refusal.test.ts index 4071e84c6a2..22a456a8021 100644 --- a/packages/plugins/plugin-security/src/permission-set-duplicate-name-refusal.test.ts +++ b/packages/plugins/plugin-security/src/permission-set-duplicate-name-refusal.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#19307] THE DUPLICATE-NAME REFUSAL on `sys_permission_set` — its ADR-0112 + * [commit 8f6d83147] THE DUPLICATE-NAME REFUSAL on `sys_permission_set` — its ADR-0112 * envelope, and the ORDER it stands in relative to the packaged-set lock. * * Two halves of one defect, both measured live on `examples/app-showcase` diff --git a/packages/plugins/plugin-security/src/permission-set-overlay-discard.ts b/packages/plugins/plugin-security/src/permission-set-overlay-discard.ts index 808c75e26f8..dacf731ca97 100644 --- a/packages/plugins/plugin-security/src/permission-set-overlay-discard.ts +++ b/packages/plugins/plugin-security/src/permission-set-overlay-discard.ts @@ -22,7 +22,7 @@ * * ## Why this bypasses `deleteMetaItem`, not calls it * - * `permission` is `allowOrgOverride: false` since #6483/#6608 (ADR-0094 + * `permission` is `allowOrgOverride: false` since commit ee58392e1 (ADR-0094 * D5-R), so the protocol's ADR-0005 tier gate refuses `deleteMetaItem` on an * artifact-backed name with 403 `NOT_OVERRIDABLE` — see * `permission-set-projection.ts`'s header, "leaving the operator hatch diff --git a/packages/plugins/plugin-security/src/permission-set-projection.test.ts b/packages/plugins/plugin-security/src/permission-set-projection.test.ts index aa4e76fc4c5..41754bf54d0 100644 --- a/packages/plugins/plugin-security/src/permission-set-projection.test.ts +++ b/packages/plugins/plugin-security/src/permission-set-projection.test.ts @@ -9,11 +9,11 @@ * * [#6858 / ADR-0094 D5-R] `makeProtocol` models ADR-0005's TIER GATE, which * this suite used to be blind to: `permission` is `allowOrgOverride: false` - * since #6483 (PR #6608), so a metadata write whose (type, name) is backed by + * since commit ee58392e1, so a metadata write whose (type, name) is backed by * a code ARTIFACT is refused 403 `NOT_OVERRIDABLE`, while an artifact-free - * name rides `allowRuntimeCreate: true` and still lands. PR #6608 recorded the - * blind spot in its own body — "its own suite stubs `saveMetaItem`, so this - * file is where that behaviour is actually pinned against the real gate" — + * name rides `allowRuntimeCreate: true` and still lands. Commit ee58392e1 records the + * blind spot in its own message: this suite stubs `saveMetaItem`, so the real + * gate is pinned elsewhere (dogfood cases and a dedicated 403 suite) — * which is exactly why four cases here kept asserting the RETIRED overlay * direction and stayed green: the stub could not refuse. They are triaged * below, each one individually. @@ -367,7 +367,7 @@ describe('upsertEnvPermissionSet (ADR-0094 — record is a pure projection)', () // package-owned row, this function projects the facets and preserves the // provenance. What D5-R retired is the CLAIM about where that body comes // from — an env overlay of a packaged set is no longer a supported - // customization channel (#6483 / PR #6608); see the D5-R lifecycle block + // customization channel (commit ee58392e1); see the D5-R lifecycle block // below for the refusal this projector now sits behind. const ql = makeQl(); ql.permRows.push({ id: 'ps_pkg', name: 'organization_admin', managed_by: 'package', package_id: 'com.example.crm', system_permissions: '["pkg"]' }); @@ -481,7 +481,7 @@ describe('registerPermissionSetProjection', () => { // RETIRED; what survives is the `allowRuntimeCreate` tier) ─────────────── /** Seed an env-scope overlay row directly — a LEGACY overlay, authored before - * the #6483 rollback closed the write door. `saveMetaItem` can no longer mint + * commit ee58392e1's rollback closed the write door. `saveMetaItem` can no longer mint * one for an artifact-backed name, but `supportsOverlay: true` is unchanged, * so rows that already exist still merge overlay-wins at read time. */ const seedLegacyOverlay = (ql: any, name: string, body: any) => { @@ -494,8 +494,8 @@ const seedLegacyOverlay = (ql: any, name: string, body: any) => { describe('package-owned set customization lifecycle (ADR-0094 D5-R)', () => { it('an env-scope save on an ARTIFACT-BACKED package name is REFUSED (403 NOT_OVERRIDABLE) — no overlay, record untouched', async () => { // Was: "a Studio env-scope save on a PACKAGE name customizes the record - // and keeps provenance" — the 2026-07-14 direction. #6483 rolled - // `permission` back to `allowOrgOverride: false` and #6609 ruling A + // and keeps provenance" — the 2026-07-14 direction. Commit ee58392e1 rolled + // `permission` back to `allowOrgOverride: false` and ADR-0094 D5-R's ruling A // accepted the tightening, so the write this case used to assert is the // write production now refuses. Rejection-class: the ENVELOPE is the // claim (`code` AND `status`), because a bare "it threw" would stay green @@ -518,7 +518,7 @@ describe('package-owned set customization lifecycle (ADR-0094 D5-R)', () => { }); it('a package row MATERIALIZED through the metadata door is still customizable — the surviving allowRuntimeCreate tier', async () => { - // The boundary #6608 measured UNAFFECTED, and the reason D5-R names a + // The boundary commit ee58392e1 measured UNAFFECTED, and the reason D5-R names a // surviving NEIGHBOUR rather than a re-route: this row is // `managed_by:'package'` like the one above, but its DEFINITION lives in // `sys_metadata` (authored + published through the metadata door, @@ -539,7 +539,7 @@ describe('package-owned set customization lifecycle (ADR-0094 D5-R)', () => { }); it('a LEGACY overlay (authored before the rollback) still projects, and deleting it still RESETS to the declaration', async () => { - // `supportsOverlay: true` was not touched by #6483 — only the WRITE flag + // `supportsOverlay: true` was not touched by commit ee58392e1 — only the WRITE flag // was. A row that already exists keeps merging overlay-wins, so the // reset invariant still has to hold for it. Seeded directly because the // write door that used to mint it is closed. @@ -762,7 +762,7 @@ describe('createPermissionSetWriteThrough (data door → metadata store)', () => it('UPDATE of an ARTIFACT-BACKED set surfaces the producer\'s 403 to the caller (write point :794 — left to 403 loudly)', async () => { // Was: "UPDATE of a PACKAGE-OWNED set becomes an env overlay" — the // retired D5 direction. This is the ONE of the four production write - // points that the #6483 rollback actually closes, and the card's design + // points that commit ee58392e1's rollback actually closes, and the card's design // question was what to do with it. Decision (ADR-0094 D5-R): leave it to // the producer. The middleware still TRANSLATES the write; the protocol's // ADR-0005 tier gate refuses it; the middleware neither pre-empts the @@ -1289,7 +1289,7 @@ describe('reconcilePermissionSetProjection', () => { // on `ProjectionLogger` (#9754), so no TS caller can build the sink above // without saying `as unknown as` out loud. // - // ⚠️ Not pinned here with `@ts-expect-error`, and [#13176] moved the reason + // ⚠️ Not pinned here with `@ts-expect-error`, and [commit a68c61267] moved the reason // out from under that sentence. It used to be that this package's tsconfig // excluded `**/*.test.ts` (it carried a TEST_DEBT ledger entry in // scripts/check-type-check-coverage.mjs), so no tsc program compiled this diff --git a/packages/plugins/plugin-security/src/permission-set-projection.ts b/packages/plugins/plugin-security/src/permission-set-projection.ts index ac513ecb57b..63b23a6a8e9 100644 --- a/packages/plugins/plugin-security/src/permission-set-projection.ts +++ b/packages/plugins/plugin-security/src/permission-set-projection.ts @@ -29,9 +29,9 @@ * declaration as the BASELINE (boot seeding / publish materialization). The * 2026-07-14 direction confirmation that used to sit here — "the environment * customizes them through the platform's standard ADR-0005 metadata overlay" - * — is **RETIRED** (ADR-0094 D5-R, 2026-08-09; #6609 ruling A executed by - * #6858). #6483 rolled `permission` back to `allowOrgOverride: false` - * (PR #6608), so ADR-0005's security row is enforced again: an overlay of the + * — is **RETIRED** (ADR-0094 D5-R, 2026-08-09, recording ruling A, executed by + * #6858). Commit ee58392e1 rolled `permission` back to `allowOrgOverride: false` + * (the nine-type rollback), so ADR-0005's security row is enforced again: an overlay of the * authorization surface IS the "silent privilege drift" it excludes. * * What that means for THIS file, per write point: @@ -51,7 +51,7 @@ * ITSELF, before translating it, with a message that names the clone path * ({@link createPermissionSetWriteThrough}'s insert and update legs throw * it; the `restore` leg consults the same lock before its re-author and - * reports the refusal on the durability channel instead — #12020, argued + * reports the refusal on the durability channel instead — commit 9cfc1f7e9, argued * at the leg; the rule and its reasoning live in * `packaged-permission-set-lock.ts`). Two * measured reasons the producer could not carry it alone: the producer's @@ -83,7 +83,7 @@ * backfill only runs for names with NO metadata presence at all) — and the * `restore` leg no longer relies on that fence alone: it consults the * packaged-permission-set lock before re-authoring, refusing the mint on - * the same durability channel (#12020). + * the same durability channel (commit 9cfc1f7e9). * * Cross-package composition stays a POSITION concern (bind several packages' * sets to one position); package-first authoring (ADR-0070) gives @@ -479,7 +479,7 @@ const isProjectionEcho = (v: any): boolean => * and the one store the env projection never writes, so it can't be poisoned * by our own registry sync. It is the body for a declared set with no overlay * at all, and the reset target when an overlay IS lifted off one — which since - * ADR-0094 D5-R means a LEGACY (pre-#6483) row removed through the operator + * ADR-0094 D5-R means a LEGACY row (older than commit ee58392e1) removed through the operator * hatch, not a data-door delete: #6960 measures that delete refusing with 403 * `NOT_OVERRIDABLE`. * @@ -532,8 +532,8 @@ function hasSchemaRegistry(ql: any): boolean { * overlay is the platform's standard ADR-0005 customization of a packaged * definition, and deleting the overlay resets the row to the shipped * declaration" — is **RETIRED**, in BOTH halves (ADR-0094 D5-R, 2026-08-09; - * #6609 ruling A executed by #6858; the file header above records the same - * retirement). Since #6483 / PR #6608 rolled `permission` back to + * the ruling A it records, executed by #6858; the file header above records the same + * retirement). Since commit ee58392e1 rolled `permission` back to * `allowOrgOverride: false`: * * - **no new overlay of a packaged set can be minted.** A metadata write @@ -554,7 +554,7 @@ function hasSchemaRegistry(ql: any): boolean { * rollback still merges overlay-wins at read time * ({@link projectPermissionMutation} hands us `overlay ?? declared`) and * this pass still stamps the flag for it. The in-repo corpus had zero such - * rows when PR #6608 measured it; + * rows when commit ee58392e1 measured it; * - **"delete = reset" must NOT be read back into that.** #6960 measures the * ordinary delete path refusing to lift exactly such a legacy overlay: on * an environment-scoped kernel `deleteMetaItem` throws `NOT_OVERRIDABLE` / @@ -1092,7 +1092,7 @@ export function createPermissionSetWriteThrough( // package-managed row stays read-only through the data door. // // [ADR-0094 D5-R] This is no longer the ONLY reason a packaged set is - // read-only through the data door — since #6483 a CAPABLE kernel refuses + // read-only through the data door — since commit ee58392e1 a CAPABLE kernel refuses // an artifact-backed set too, at the protocol's ADR-0005 tier gate. The // remedy this message names ("edit the package and re-publish") is // therefore right on every kernel; only the stated cause is specific to @@ -1126,7 +1126,7 @@ export function createPermissionSetWriteThrough( // Let the engine un-trash the record, then re-author its definition // into metadata (the delete removed it) so the stores converge live. // - // [2026-08-24 ruling — lock the base, clone to customize; #12020] This + // [2026-08-24 ruling — lock the base, clone to customize; commit 9cfc1f7e9] This // leg consults the SAME lock as the insert and update legs — but the // refusal lands on the durability channel instead of being thrown, and // the engine un-trash above it stands. The placement is argued, not @@ -1238,7 +1238,7 @@ export function createPermissionSetWriteThrough( // Refused here, before the write, with a message that names the clone // path. Fail-closed: unresolvable provenance refuses too. // - // [#19307] ⭐ It runs BEFORE the duplicate-name check below, and the + // [commit 8f6d83147] ⭐ It runs BEFORE the duplicate-name check below, and the // order is the fix rather than a tidy-up. A package-declared set has a // PROJECTED ROW, so its name is duplicate AND locked at once — and the // admin most likely to arrive here is the one who opened the Clone @@ -1260,7 +1260,7 @@ export function createPermissionSetWriteThrough( assertPermissionSetNotPackageDeclared( name, ql, 'insert', (await probeLayered(protocol, name)).probe, ); - // [#19307] The duplicate-name refusal carries `UNIQUE_VIOLATION` — the + // [commit 8f6d83147] The duplicate-name refusal carries `UNIQUE_VIOLATION` — the // wire identity this collision already has when the `name` index // catches it instead. See `PermissionSetNameConflictError`. const dup = (await tryFind(ql, 'sys_permission_set', { name }, 1))[0]; diff --git a/packages/plugins/plugin-security/src/platform-owner-wall-bypass.test.ts b/packages/plugins/plugin-security/src/platform-owner-wall-bypass.test.ts index b4a5d916a05..f530ca8e669 100644 --- a/packages/plugins/plugin-security/src/platform-owner-wall-bypass.test.ts +++ b/packages/plugins/plugin-security/src/platform-owner-wall-bypass.test.ts @@ -8,7 +8,7 @@ * 时候不要强制加上 org_id 的过滤」— when plugin-security arms the Layer 0 * organization wall, the `org_id` filter is NOT appended for a session whose * account is the VERIFIED declared platform owner (`OS_PLATFORM_OWNER_EMAIL` - * under the #11343 verified-email predicate). Everyone else's wall is + * under commit c0714eb5d's verified-email predicate). Everyone else's wall is * byte-identical to before. * * The pins hold BOTH fail-closed directions the ruling records (there is no @@ -167,7 +167,7 @@ describe('[#12974] verified-platform-owner Layer 0 wall bypass — fail-closed d it('email matches but the account is NOT verified ⇒ still walled', async () => { process.env.OS_PLATFORM_OWNER_EMAIL = OWNER_EMAIL; const { plugin } = await boot({ - // No `email_verified` at all — the #11343 allow-list reads absent as + // No `email_verified` at all — commit c0714eb5d's allow-list reads absent as // unverified (the imported/legacy-row shape). users: { u_owner: { id: 'u_owner', email: OWNER_EMAIL } }, }); diff --git a/packages/plugins/plugin-security/src/platform-owner-wall-bypass.ts b/packages/plugins/plugin-security/src/platform-owner-wall-bypass.ts index c01bc04df5c..5cb5f6941b5 100644 --- a/packages/plugins/plugin-security/src/platform-owner-wall-bypass.ts +++ b/packages/plugins/plugin-security/src/platform-owner-wall-bypass.ts @@ -2,7 +2,7 @@ /** * [#12974] The VERIFIED-platform-owner row predicate — the one comparison the - * #11343 verified-owner family makes, extracted so its two in-package + * verified-owner family (commit c0714eb5d) makes, extracted so its two in-package * consumers can never drift: * * - **The platform-admin standing surface** (`platform-admin-service.ts`, @@ -66,7 +66,7 @@ export const PLATFORM_OWNER_WALL_BYPASS_EVENT = 'platform_owner_wall_bypass'; /** * Does this `sys_user` row's email match ONE OF the env-declared platform - * administrators — the canonical #11184/#11343 comparison (trimmed, + * administrators — the canonical #11184 / commit c0714eb5d comparison (trimmed, * case-insensitive), asked of the ONE parser. * * [#13147] `config` is `resolvePlatformAdminEmails()`'s output, never a raw @@ -81,7 +81,7 @@ export function matchesDeclaredOwnerEmail(row: unknown, config: PlatformAdminEma /** * Is this `sys_user` row the VERIFIED declared platform owner? — the whole * predicate the Layer 0 owner wall bypass keys on: declared-owner email - * match AND the #11343 verified-email allow-list. Server-side row facts + * match AND commit c0714eb5d's verified-email allow-list. Server-side row facts * only; never a client-supplied claim. */ export function isVerifiedPlatformOwnerRow(row: unknown, config: PlatformAdminEmailConfig): boolean { diff --git a/packages/plugins/plugin-security/src/platform-ownership-policies.ts b/packages/plugins/plugin-security/src/platform-ownership-policies.ts index 2bc5f3a01af..38508884ae6 100644 --- a/packages/plugins/plugin-security/src/platform-ownership-policies.ts +++ b/packages/plugins/plugin-security/src/platform-ownership-policies.ts @@ -137,7 +137,7 @@ export function platformOwnershipFloorPolicyCount(): number { * - `controlled_by_parent` derives its access from the MASTER record, which * has its own OWD and its own gate (`assertControlledByParentWrite`) — the * detail declares nothing about who may write it. ⚠️ **That reasoning is - * about THIS constant and stops here** (#8757). It says a detail is not + * about THIS constant and stops here** (commit 6feac910b). It says a detail is not * org-wide-open, and it is still right about that. It was ALSO read as * saying the master gate governs a detail's by-id writes — and when this * bullet was written that was not true of the runtime: the floor answered @@ -186,7 +186,7 @@ export function owdOpenWritesCoversOperation(operation: string): boolean { } /** - * [#8757] The OWD whose row-level write authority is ANOTHER OBJECT's gate. + * [commit 6feac910b] The OWD whose row-level write authority is ANOTHER OBJECT's gate. * * Maintainer ruling 2026-08-15 (delegated adjudication), on the card that * measured a `controlled_by_parent` detail refusing a cross-creator by-id diff --git a/packages/plugins/plugin-security/src/plugin-keyed-text-bounds.test.ts b/packages/plugins/plugin-security/src/plugin-keyed-text-bounds.test.ts index 1a94d3e7f9b..677ad6a9631 100644 --- a/packages/plugins/plugin-security/src/plugin-keyed-text-bounds.test.ts +++ b/packages/plugins/plugin-security/src/plugin-keyed-text-bounds.test.ts @@ -7,10 +7,10 @@ import { SecurityPlugin } from './security-plugin.js'; * The suggestion key columns carry their REFERENCED columns' bounds, and the * composite key stays expressible on MySQL. * - * ## What used to be here, and where it went (#12147) + * ## What used to be here, and where it went (commit 945e91a13) * * This file carried route A's rule — "every text-family column a declared index - * keys on declares a `maxLength`" (#11374) — enumerated over the objects this + * keys on declares a `maxLength`" (commit 3954fb7df) — enumerated over the objects this * plugin registers, with a vacuity control and an `UNBOUNDABLE` allowlist. That * is now `scripts/check-keyed-text-bounds.mjs`, a source scan over EVERY * `*.object.ts` in the repository. The rationale for the move, and why a diff --git a/packages/plugins/plugin-security/src/record-share-tenant-wall.test.ts b/packages/plugins/plugin-security/src/record-share-tenant-wall.test.ts index 8aa478da0ce..fb29bdb1958 100644 --- a/packages/plugins/plugin-security/src/record-share-tenant-wall.test.ts +++ b/packages/plugins/plugin-security/src/record-share-tenant-wall.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#14484] A tenant-scoped read of `sys_record_share` under plugin-security's + * [commit 3f64fe6c6] A tenant-scoped read of `sys_record_share` under plugin-security's * Layer 0 returns the same grants the bare-context reads return for that * organization — the cliff the card named, closed and pinned. * diff --git a/packages/plugins/plugin-security/src/rls-accessible-org-ids-plumbing.test.ts b/packages/plugins/plugin-security/src/rls-accessible-org-ids-plumbing.test.ts index 1c099ffc497..dafbdde98c7 100644 --- a/packages/plugins/plugin-security/src/rls-accessible-org-ids-plumbing.test.ts +++ b/packages/plugins/plugin-security/src/rls-accessible-org-ids-plumbing.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#16518] `current_user.accessible_org_ids` must RESOLVE — the plumbing, pinned + * [commit 470746ae4] `current_user.accessible_org_ids` must RESOLVE — the plumbing, pinned * end to end. * * ## The contradiction this file closes diff --git a/packages/plugins/plugin-security/src/rls-check-membership-staging.test.ts b/packages/plugins/plugin-security/src/rls-check-membership-staging.test.ts index 105c5dc9902..faf1b938b53 100644 --- a/packages/plugins/plugin-security/src/rls-check-membership-staging.test.ts +++ b/packages/plugins/plugin-security/src/rls-check-membership-staging.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#16607] An RLS `check` clause that reads a membership-resolver key must + * [commit 1d73d45c1] An RLS `check` clause that reads a membership-resolver key must * resolve on a BARE insert — the write path stages `rlsMembership` itself. * * MEASURED on 17.3.0 (the card's table, `--database-driver memory` and the @@ -305,7 +305,7 @@ async function makeStack(resolver: Resolver | null): Promise { try { await securityMw(opCtx, async () => { await sharingMw(opCtx, async () => { - // [#16608] The engine's own half of the write gate, which this + // [commit a016f08b8] The engine's own half of the write gate, which this // executor stands in for: the insert-side RLS `check` is INSTALLED on // the operation context by the middleware and run by `ObjectQL.insert` // once the `beforeInsert` chain has produced the row that will be diff --git a/packages/plugins/plugin-security/src/rls-compiler.ts b/packages/plugins/plugin-security/src/rls-compiler.ts index 9fcb366d8b1..fbc9822d80f 100644 --- a/packages/plugins/plugin-security/src/rls-compiler.ts +++ b/packages/plugins/plugin-security/src/rls-compiler.ts @@ -85,7 +85,7 @@ interface RLSUserContext { * `rlsMembership` bag, because a wall an app could redefine would not be a * wall. * - * ⚠️ Reserving a key obliges someone to FILL it. Until #16518 nobody did: + * ⚠️ Reserving a key obliges someone to FILL it. Until commit 470746ae4 nobody did: * `packages/spec` declared the key's SHAPE (`accessible_org_ids?: string[]`) * and named core as its resolver, an app was refused from supplying it, and * this interface did not carry it — so every predicate naming it dropped out @@ -543,7 +543,7 @@ export class RLSCompiler { organization_id: executionContext?.tenantId, positions: executionContext?.positions, org_user_ids: (executionContext as any)?.org_user_ids, - // [ADR-0105 D2 / #16518] The caller's union org scope, copied from the + // [ADR-0105 D2 / commit 470746ae4] The caller's union org scope, copied from the // execution context exactly as `org_user_ids` is. Both are core-resolved // membership sets the runtime pre-resolves so this compiler never has to // issue a subquery; the ONLY reason this line was missing is that nobody diff --git a/packages/plugins/plugin-security/src/rls-pushdown-limits.test.ts b/packages/plugins/plugin-security/src/rls-pushdown-limits.test.ts index 4a3e16ec8d1..5332645fbdf 100644 --- a/packages/plugins/plugin-security/src/rls-pushdown-limits.test.ts +++ b/packages/plugins/plugin-security/src/rls-pushdown-limits.test.ts @@ -53,7 +53,7 @@ function compilerWithLogger() { return { compiler, logger }; } -// [#13176] `ReturnType` instantiates that generic's own type +// [commit a68c61267] `ReturnType` instantiates that generic's own type // parameters, so `mock.calls` came back untyped and every callback over it was // an implicit `any` — invisible while no tsc program read this file. Naming the // spied signature types the call records instead of annotating each callback. diff --git a/packages/plugins/plugin-security/src/row-write-widener-composition.test.ts b/packages/plugins/plugin-security/src/row-write-widener-composition.test.ts index d1b00fb2d75..bb04281584d 100644 --- a/packages/plugins/plugin-security/src/row-write-widener-composition.test.ts +++ b/packages/plugins/plugin-security/src/row-write-widener-composition.test.ts @@ -25,7 +25,7 @@ // Maintainer ruling (2026-08-07, issue comment 5219846435): "enforce both // declared write-widening mechanisms. The row-level write gate must consult // `modifyAllRecords` (profile axis) and `sys_record_share.access_level = -// 'edit'` (share axis)." Route: PR #6564's tri-state `ISharingService` verdict, +// 'edit'` (share axis)." Route: commit 54299caad's tri-state `ISharingService` verdict, // composed by provenance — `allow` replaces the platform floor, `abstain` and // `deny` leave it standing. // diff --git a/packages/plugins/plugin-security/src/security-plugin.test.ts b/packages/plugins/plugin-security/src/security-plugin.test.ts index 5c79ea0224a..8027f814948 100644 --- a/packages/plugins/plugin-security/src/security-plugin.test.ts +++ b/packages/plugins/plugin-security/src/security-plugin.test.ts @@ -16,10 +16,10 @@ import { RLS } from '@objectstack/spec/security'; import { BUILTIN_OPERATION_MESSAGES } from '@objectstack/spec/system'; /** - * [#16608] What the ENGINE does inside the middleware's `next()` — the part of + * [commit a016f08b8] What the ENGINE does inside the middleware's `next()` — the part of * `ObjectQL.insert` the doubles in this file stand in for. * - * Since #16608 the insert-side RLS `check` is not evaluated in the middleware: + * Since commit a016f08b8 the insert-side RLS `check` is not evaluated in the middleware: * it is INSTALLED on the operation context and run by the engine once the * `beforeInsert` chain has produced the row that will be stored. A double whose * executor is a bare `async () => {}` therefore models an engine that carries a @@ -146,7 +146,7 @@ describe('SecurityPlugin', () => { // The predicate itself is pinned exhaustively next to its producer // (bootstrap-platform-admin-walled-owner.test.ts); THIS pin is that the // middleware actually consults it. [#11974 / #11663 L4] The trigger set is - // NARROWED: the #11343 update arm (email_verified / email) retired with the + // NARROWED: commit c0714eb5d's update arm (email_verified / email) retired with the // walled elevation it existed to re-attempt — under `single` (this suite's // posture) only a sys_user insert/create can change the promotion answer. // ------------------------------------------------------------------------- @@ -2183,8 +2183,8 @@ describe('SecurityPlugin', () => { it('PASSES an admin update of a package-managed set at THIS gate (ADR-0094: the refusal is the write-through producer\'s, not this gate\'s)', async () => { // update/delete on a package row are not refused at this gate — the // ADR-0094 write-through downstream translates them into a metadata - // write, and that producer decides. Since ADR-0094 D5-R (#6483 / - // PR #6608) the answer for a CODE-DECLARED set is 403 NOT_OVERRIDABLE, + // write, and that producer decides. Since ADR-0094 D5-R (commit + // ee58392e1) the answer for a CODE-DECLARED set is 403 NOT_OVERRIDABLE, // so "the write-through turns it into an env overlay" is no longer why // this passes; it passes because the gate's job is forging provenance, // not overridability. The refusal is covered in diff --git a/packages/plugins/plugin-security/src/security-plugin.ts b/packages/plugins/plugin-security/src/security-plugin.ts index faa10fb1e8a..5313307af7c 100644 --- a/packages/plugins/plugin-security/src/security-plugin.ts +++ b/packages/plugins/plugin-security/src/security-plugin.ts @@ -346,7 +346,7 @@ interface RlsFilterOptions { * a row record sharing does not enforce on keeps the floor as its only * row-level write gate. * - * [#8865] TWO call sites set it, and that is the point rather than a + * [commit 498f4e884] TWO call sites set it, and that is the point rather than a * duplication: the by-id write pre-image gate (step 2.7) asks about the row * being written, and ADR-0055's master gate * ({@link SecurityPlugin.assertControlledByParentWrite}, step 2.8, leg 1) asks @@ -361,7 +361,7 @@ interface RlsFilterOptions { */ dropPlatformOwnershipFloor?: boolean; /** - * [#8757] The caller VOUCHES that ADR-0055's master gate + * [commit 6feac910b] The caller VOUCHES that ADR-0055's master gate * ({@link SecurityPlugin.assertControlledByParentWrite}, step 2.8) runs on * this exact operation, for this exact principal, after this filter is * enforced — so a `controlled_by_parent` object's platform ownership floor @@ -401,7 +401,7 @@ interface RlsFilterOptions { * {@link SecurityPlugin.resolveCbpRelation} and cached. */ /** - * [#11082] How many `controlled_by_parent` hops the master-set derivation and + * [ADR-0055 amendment] How many `controlled_by_parent` hops the master-set derivation and * the master-write gate will walk before they fail CLOSED. * * ⚠️ This is a COST ceiling, not a semantic rule, and it is deliberately not a @@ -418,14 +418,14 @@ interface RlsFilterOptions { * (`showcase_invoice_line` → `showcase_invoice`, `showcase_expense_line` → * `showcase_expense_report`, `crm_opportunity_line_item` → `crm_opportunity`) * — in each case the master's own model is `public_read_write` or `private`, - * never derived. The consumer that motivated #11082 needs **two** + * never derived. The consumer that motivated the ADR-0055 amendment needs **two** * (`crm_quote_line_item` → `crm_quote` → `crm_account`). 8 is four times the * deepest chain any consumer has asked for, so it cannot be reached by * authoring that means anything, and it still caps the walk at 8 queries. * * AT THE BOUND: the read derivation returns the EMPTY master set and the write * gate DENIES, each logging the chain it refused. ⛔ Never "no restriction" — - * that is precisely the failure #11082 fixed, and a bound that widened on + * that is precisely the failure the ADR-0055 amendment closed, and a bound that widened on * overflow would reintroduce it at depth 9 instead of depth 2. */ const CBP_MAX_CHAIN_DEPTH = 8; @@ -449,7 +449,7 @@ interface CbpRelation { * the stand-down site for what a bare hand-over would mint. * * ⛔ [#9137] Do not widen this predicate to `true` for `readonly`/`system`, or - * to drop the `master_detail`+`required` condition, until #8772's ramp + * to drop the `master_detail`+`required` condition, until the ramp commit 8abada3ba records * completes (#9138 builder-force + #9139 lint-at-v18). See the freeze note at * the stand-down site (below, in {@link SecurityPlugin.assertControlledByParentWrite}) * for why. @@ -2038,7 +2038,7 @@ export class SecurityPlugin implements Plugin { // `update`/`delete` on a package row are handled downstream by the // ADR-0094 write-through, which TRANSLATES them into a metadata write. // Whether that write is ACCEPTED is ADR-0005's call, not this gate's, - // and since ADR-0094 D5-R (#6483 / PR #6608 rolled `permission` back to + // and since ADR-0094 D5-R (commit ee58392e1 rolled `permission` back to // `allowOrgOverride: false`) a CODE-DECLARED set is refused there with // 403 `NOT_OVERRIDABLE` — the 2026-07-14 "customize / reset via an env // overlay" direction this comment used to state is RETIRED. @@ -2388,7 +2388,7 @@ export class SecurityPlugin implements Plugin { // stamps the server-DERIVED `__referentialFieldClear` marker on its // context (#3023). The marker cannot be forged from a request — // `assembleExecutionContext` builds an inbound envelope from a CLOSED - // field set and no `__` operation-private key is in it (#6216 / #7284). + // field set and no `__` operation-private key is in it (commit f586f1a89 / #7284). // // What it fixes: a role holding full delete rights on A and NO grant at // all on B could delete an A only while B was EMPTY. The moment a real row @@ -2649,7 +2649,7 @@ export class SecurityPlugin implements Plugin { permissionSets, !!delegatorSets, ); - // [#8757] The COVERAGE VOUCH for ADR-0055 details. This gate and the + // [commit 6feac910b] The COVERAGE VOUCH for ADR-0055 details. This gate and the // master gate (step 2.8, immediately below) run on the same middleware // pass, and 2.8's condition is a strict SUPERSET of this one: the same // `permissionSets.length > 0 && userId && this.ql` triple, the same @@ -3075,7 +3075,7 @@ export class SecurityPlugin implements Plugin { // exact composition, and the ownership-floor exception, live on // `writeCheckPolicies`. // - // ── [#16608] WHICH IMAGE, on an INSERT ──────────────────────────────── + // ── [commit a016f08b8] WHICH IMAGE, on an INSERT ────────────────────── // // Both verbs judge THE ROW THAT WILL EXIST. `update` has a pre-image to // merge the change set onto here, though only the change set AS SENT @@ -3762,7 +3762,7 @@ export class SecurityPlugin implements Plugin { await next(); - // [#16608] FAIL CLOSED on a seam that was never run. `honoured` is set by + // [commit a016f08b8] FAIL CLOSED on a seam that was never run. `honoured` is set by // the engine immediately before it calls the judgement, so an unset flag // means one thing only: the write went past without its stored-row // `check` being evaluated at all — an engine that does not implement the @@ -4231,7 +4231,7 @@ export class SecurityPlugin implements Plugin { // Feature-detected; protocols predating registerAuthoringGate keep // the legacy (CLI-lint-only) behavior. registerObjectPostureGate(protocol); - // [#11843 — maintainer ruling 2026-08-25, option B] The packaged- + // [commit 5619aace3 — maintainer ruling 2026-08-25, option B] The packaged- // permission-set lock's METADATA-door registration: the same // classifier and error classes the data door runs // (`packaged-permission-set-lock.ts`), now consulted on the @@ -4459,7 +4459,7 @@ export class SecurityPlugin implements Plugin { // postures the bootstrap writes no grant — standing is config-derived // at request time (`resolve-authz-context.ts` §6b-config) — so no // `sys_user` write can change its answer and the replay never fires. - // The #11343 UPDATE arm (`email_verified` / `email`) retired with the + // Commit c0714eb5d's UPDATE arm (`email_verified` / `email`) retired with the // walled elevation it existed to re-attempt. // // The trigger set is `shouldReplayBootstrapFor` — the SAME predicate its @@ -6450,8 +6450,8 @@ export class SecurityPlugin implements Plugin { // [ADR-0094 D5-R] `update`/`delete` on a package-managed row are not // refused HERE: the write-through middleware (which runs after this gate // + the delegated-admin gate + the CRUD checks) translates them into a - // metadata write, and the refusal is LEFT TO THAT PRODUCER. Since #6483 / - // PR #6608 rolled `permission` back to `allowOrgOverride: false`, a + // metadata write, and the refusal is LEFT TO THAT PRODUCER. Since commit + // ee58392e1 rolled `permission` back to `allowOrgOverride: false`, a // CODE-DECLARED (artifact-backed) set is refused there with 403 // `NOT_OVERRIDABLE`; a `sys_metadata`-backed set rides // `allowRuntimeCreate` and still lands. The 2026-07-14 "customize / reset @@ -6720,7 +6720,7 @@ export class SecurityPlugin implements Plugin { } /** - * [#8757] Does this object declare that its access — writes included — derives + * [commit 6feac910b] Does this object declare that its access — writes included — derives * from a master record (ADR-0055 `controlled_by_parent`)? * * ## One read point, deliberately, and this is the whole reason it exists @@ -7038,7 +7038,7 @@ export class SecurityPlugin implements Plugin { // check (`computeWriteCheckFilter`) asks too, so the floor a defaulted // check composes is the floor this pre-image composed. // - // [#8757] An ADR-0055 `controlled_by_parent` detail does not inherit the + // [commit 6feac910b] An ADR-0055 `controlled_by_parent` detail does not inherit the // platform's wildcard write ownership floor either — for a DIFFERENT // reason from the OWD above, and under one extra condition. // @@ -7259,7 +7259,7 @@ export class SecurityPlugin implements Plugin { // `group` union, or the fail-closed deny sentinel an org-less session // otherwise hits), the org filter is NOT appended for a session whose // account is the VERIFIED declared platform owner (`OS_PLATFORM_OWNER_EMAIL` - // under the #11343 verified-email predicate — the same match the elevation + // under commit c0714eb5d's verified-email predicate — the same match the elevation // gate makes; see `isVerifiedPlatformOwnerSession` for the fail-closed // ladder). Everyone else's wall is byte-identical to before: the probe // answers `false` on env-unset before touching any row, and it is not even @@ -7344,7 +7344,7 @@ export class SecurityPlugin implements Plugin { * with the platform-admin standing surface — it was extracted as the * elevation gate's twin, and since the #11663 re-anchor retired that * gate its opposite number is the per-request derivation at - * `resolve-authz-context.ts` §6b-config) AND the #11343 verified-email + * `resolve-authz-context.ts` §6b-config) AND commit c0714eb5d's verified-email * allow-list (`isEmailVerifiedUserRow` — absent-means-unverified). * Missing row / unreadable store ⇒ `false`. * @@ -7435,7 +7435,7 @@ export class SecurityPlugin implements Plugin { { keepOwnershipFloor: !floorReplaced }, ); if (withCheck.length === 0) return null; - // [ADR-0105 D11 / #16607] Stage the app-resolved membership sets on THIS + // [ADR-0105 D11 / commit 1d73d45c1] Stage the app-resolved membership sets on THIS // context before the `check` clause compiles — the same staging the read // side performs before Layer 1 compiles (`computeLayeredRlsFilter`). A // bare insert performs no read, so without this line the `check` twin of @@ -7507,7 +7507,7 @@ export class SecurityPlugin implements Plugin { * `check` compiles). A predicate must resolve the same variables whichever * clause it sits in; with the write-side call missing, a `check` reading a * resolver key resolved only when the request happened to read first - * (#16607). + * (commit 1d73d45c1). */ private async stageRlsMembership(context: any): Promise { if (!this.rlsMembershipResolver || !context || typeof context !== 'object') return; @@ -7801,7 +7801,7 @@ export class SecurityPlugin implements Plugin { * (defense-in-depth; spec validation should prevent authoring it). Returns null * when the object is not controlled_by_parent. * - * [#11082] The derivation COMPOSES ACROSS A CHAIN. It used to resolve the + * [ADR-0055 amendment] The derivation COMPOSES ACROSS A CHAIN. It used to resolve the * master set from the two halves above and nothing else, which made a master * that is ITSELF `controlled_by_parent` resolve to "no restriction" on both: * its RLS half is `null` (a derived object authors no policy — that is the @@ -7839,7 +7839,7 @@ export class SecurityPlugin implements Plugin { object: string, context: any, /** - * [#11082] The `controlled_by_parent` objects already being resolved on + * [ADR-0055 amendment] The `controlled_by_parent` objects already being resolved on * this branch of the walk, outermost first. Empty at every real call site * — the four are the CRUD middleware (caller and D10 delegator) and * `getReadFilter` — and grown by one on each recursive hop. @@ -7854,7 +7854,7 @@ export class SecurityPlugin implements Plugin { const rel = this.resolveCbpRelation(object); if (!rel) return { ...RLS_DENY_FILTER }; - // [#11082] Chain guards, BEFORE any store work. Both answer with the empty + // [ADR-0055 amendment] Chain guards, BEFORE any store work. Both answer with the empty // master set — the same shape the #5386 sharing-resolution failure answers // with, and the same posture: a chain this derivation cannot resolve denies, // because the alternative ("no restriction") is the defect being fixed. @@ -7891,7 +7891,7 @@ export class SecurityPlugin implements Plugin { ); return { [rel.fk]: { $in: [] } }; } - // [#11082] The THIRD half — the master's OWN `controlled_by_parent` + // [ADR-0055 amendment] The THIRD half — the master's OWN `controlled_by_parent` // derivation, resolved through this very method so the recursion cannot // drift from the top-level answer. `null` for a master that is not derived // (the single-level case, unchanged), and internally fail-closed at every @@ -7948,7 +7948,7 @@ export class SecurityPlugin implements Plugin { * details under masters they could neither read nor edit. The sharing gate is * therefore asked UNCONDITIONALLY, not only when half 1 produced a filter. * - * [#8865] Half 1 composes the master's write RLS with the SAME ownership + * [commit 498f4e884] Half 1 composes the master's write RLS with the SAME ownership * authority the by-id write pre-image gate does — `resolveSharingWriteVerdict` * on the master row, and the platform ownership floor comes off on `allow` * (maintainer ruling 2026-08-15, direction 1). Before that, the floor stood @@ -7960,7 +7960,7 @@ export class SecurityPlugin implements Plugin { * `sys_record_share`, `modifyAllRecords`) now reaches the master's children, * which is exactly the set that already reaches the master itself. * - * [#11082] The gate WALKS THE CHAIN. Its three legs used to run once, on the + * [ADR-0055 amendment] The gate WALKS THE CHAIN. Its three legs used to run once, on the * immediate master, and every one of them passes vacuously when that master is * itself `controlled_by_parent`: it authors no write RLS, and the sharing leg * asks `canEdit`, which answers `abstain` for it — `effectiveSharingModel` @@ -7975,7 +7975,7 @@ export class SecurityPlugin implements Plugin { * * v1 scope: single-id writes. Bulk writes flow through the AST and are already * scoped by the controlled-by-parent READ filter (to readable masters) — which - * since #11082 is itself chain-composed, so the two faces still agree. + * since the ADR-0055 amendment is itself chain-composed, so the two faces still agree. * * [#7474] SIX conditions refuse a write here, and they are NOT one verdict. * Three are genuine authorization answers (no object-level `update` on the @@ -8024,7 +8024,7 @@ export class SecurityPlugin implements Plugin { // declaration / missing row / null master FK) are not verdicts at all and // throw their own errors below — see `./errors.ts` for the ruling and the // reasoning behind each code. - // [#11082] Split into a FACTORY plus the `never`-returning thrower it backs. + // [commit 61713314e] Split into a FACTORY plus the `never`-returning thrower it backs. // Both spell the same sentence, from one place. The factory exists because // TypeScript's control-flow analysis does not narrow through a `const` arrow // that returns `never` — the same reason the `!rel` branch below throws @@ -8090,8 +8090,8 @@ export class SecurityPlugin implements Plugin { // // [#8959, re-measured 2026-09-01] "Confined" is now a PARTIAL publish-time // bound: of the three shapes above, ONE is fenced at authoring time and - // two are not. #8772 was RULED (2026-08-16, comment 5306089973) and the - // ramp it ordered has two code legs, of which exactly one has landed. + // two are not. The 2026-08-16 ruling (recorded in commit 8abada3ba) ordered a + // ramp with two code legs, of which exactly one has landed. // Direction 2 (#9138) IS merged: `ObjectSchema.create()` now runs // `forceCbpMasterDetailRequired` (`packages/spec/src/data/object.zod.ts`) // — under `controlled_by_parent`, a `master_detail` reference with @@ -8129,12 +8129,12 @@ export class SecurityPlugin implements Plugin { // before trusting it — it goes stale when #9139 lands, or when the // builder force grows to cover the two flagged shapes. // - // ⛔ [#9137] FREEZE NOTE — maintainer ruling on #8772, Direction 4, + // ⛔ [#9137] FREEZE NOTE — maintainer ruling (commit 8abada3ba), Direction 4, // "immediately": until the two legs above both land, this `if` is the // SOLE ENFORCEMENT POINT for the same three authorable // `controlled_by_parent` master-reference shapes — `master_detail` with no // `required`; `required: true` + `readonly`; `required: true` + `system`. - // They are the last three rows of #8772's five-shape measurement table, + // They are the last three of the five measured shapes behind that ruling, // and exactly the three shapes `record-validator.ts` skips before its // required check ever runs: `validateRecord()` opens BOTH of its field // loops with `if (def.system || def.readonly) continue;` — the @@ -8175,7 +8175,7 @@ export class SecurityPlugin implements Plugin { throw new MasterReferenceMissingError(object, operation, rel.fk, detailRecordId); } - // [#11082] Walk the `controlled_by_parent` chain, one hop at a time, and run + // [ADR-0055 amendment] Walk the `controlled_by_parent` chain, one hop at a time, and run // the SAME three master-edit legs on every hop. // // The three legs below used to run exactly once, on the immediate master. @@ -8258,9 +8258,9 @@ export class SecurityPlugin implements Plugin { } /** - * [#5386 / #8865 / #8679] The three legs that decide whether ONE principal may + * [#5386 / commit 498f4e884 / #8679] The three legs that decide whether ONE principal may * EDIT ONE master row — extracted verbatim from - * {@link SecurityPlugin.assertControlledByParentWrite} so that [#11082]'s + * {@link SecurityPlugin.assertControlledByParentWrite} so that the ADR-0055 amendment's * chain walk can run them on every hop instead of only the first. * * ⚠️ Extraction, not a rewrite: the parameter is the `CbpRelation` itself, so @@ -8285,7 +8285,7 @@ export class SecurityPlugin implements Plugin { if (!this.permissionEvaluator.checkObjectPermission('update', rel.master, permissionSets)) { denyMasterEdit(`no edit permission on master '${rel.master}'`, masterId); } - // [#8865] The master's own write RLS — composed with the SAME ownership + // [commit 498f4e884] The master's own write RLS — composed with the SAME ownership // authority the by-id write pre-image gate (step 2.7) composes with, which // is the whole of this card. // @@ -8317,7 +8317,7 @@ export class SecurityPlugin implements Plugin { // below: this gate's question is EDIT access to the master, never the // detail's own verb; // • `masterGateCoversThisWrite` is deliberately NOT set. That knob is - // #8757's, and it hands a `controlled_by_parent` object's floor to THIS + // commit 6feac910b's, and it hands a `controlled_by_parent` object's floor to THIS // gate; setting it here would hand a nested master's floor to a gate that // is already running, on a path no measurement covers. A master that is // itself a detail keeps its floor exactly as it does today. @@ -8331,7 +8331,7 @@ export class SecurityPlugin implements Plugin { // step 2.8's guard has already established `permissionSets.length > 0`, and // a link naming a non-existent delegator throws before either call.) Reading // `context` instead would drop the floor for the delegator's pass and keep - // it for the agent's — the half-state #8757 recorded as a residual rather + // it for the agent's — the half-state commit 6feac910b left as a residual rather // than resolve by a guess. The delegated write keeps BOTH floors, exactly as // before this change. const delegatedWrite = !!opCtx?.context?.onBehalfOf?.userId; diff --git a/packages/plugins/plugin-security/src/seed-name-lookup.ts b/packages/plugins/plugin-security/src/seed-name-lookup.ts index 4d7c35fea52..36e6be731e6 100644 --- a/packages/plugins/plugin-security/src/seed-name-lookup.ts +++ b/packages/plugins/plugin-security/src/seed-name-lookup.ts @@ -37,7 +37,7 @@ * - a response that is neither an array * nor `{ records: [...] }` → could not answer * - a page carrying MORE rows than it - * budgeted for (#11518, below) → could not answer + * budgeted for (commit e1d773eb7) → could not answer * - `[]` → ANSWERED: none of these names exist * * "Could not answer" degrades — loudly warned — to the per-item read the loops @@ -57,7 +57,7 @@ * Directive #12): the batched and per-item reads ask the driver the same * question, and the answer has one meaning. * - * ## The page budget, and why it is measured rather than trusted (#11518) + * ## The page budget, and why it is measured rather than trusted (commit e1d773eb7) * * A `LIMIT` cannot express "one row per name", so the page needs a cap and no * cap is CORRECT. `limit: names.length` was exact only while one row could exist @@ -195,10 +195,10 @@ export interface ExistingByNameIndex { } /** - * [#11518] Rows per requested name an UNSCOPED page is willing to hold before + * [commit e1d773eb7] Rows per requested name an UNSCOPED page is willing to hold before * it stops trying to answer in one read. * - * ⚠️ A BUDGET, not a bound — the distinction is the whole of #11518. Nothing + * ⚠️ A BUDGET, not a bound — the distinction is the whole of commit e1d773eb7. Nothing * bounds rows-per-name here: `sys_capability.name` and `sys_permission_set.name` * are unique PER ORGANIZATION (#8461 / ADR-0120 D1) and ADR-0066 D1 encourages * admins to EXTEND the registry inside their own organization, so one name @@ -222,7 +222,7 @@ const UNSCOPED_PAGE_FLOOR = 20; * index is unique per organization, so each name has at most this organization's * row plus one organization-less leftover. Kept exact deliberately: a scoped * page that overflows it means the uniqueness the catalog is built on is not - * holding, and #11518's probe turns that into a loud degradation instead of a + * holding, and commit e1d773eb7's probe turns that into a loud degradation instead of a * silent truncation. */ const SCOPED_ROWS_PER_NAME = 2; @@ -246,7 +246,7 @@ type NamePage = * Read one page of names — `ok: false`, distinct from an empty page, when this * read cannot answer. * - * ## [#11518] Truncation is "could not answer", not "none of them exist" + * ## [commit e1d773eb7] Truncation is "could not answer", not "none of them exist" * * A `LIMIT` cannot say "one row per name", so any cap this read picks can be * exceeded by a healthy install (see {@link UNSCOPED_ROWS_PER_NAME}). The rows @@ -283,12 +283,12 @@ async function readNamePage( rows = await ql.find( object, { - // [#11451] `...(equals ?? {})` spreads NOTHING when no predicate was + // [commit c33f18592] `...(equals ?? {})` spreads NOTHING when no predicate was // given, so a caller that passes none emits the exact key set it // emitted before — not the same keys plus `undefined`-valued ones, // which `toEqual` would have quietly accepted. where: { name: { $in: names }, ...(equals ?? {}) }, - // [#11518] ONE MORE than the budget, always — the extra row is the + // [commit e1d773eb7] ONE MORE than the budget, always — the extra row is the // probe, and reading it back is how truncation is told from a page that // merely happens to be full. limit: budget + 1, @@ -337,7 +337,7 @@ function perItemIndex( // driver ordered first, and this read must be able to tell this // organization's row from an organization-less leftover. // - // [#11451] The predicate rides the DEGRADATION read too. A fallback + // [commit c33f18592] The predicate rides the DEGRADATION read too. A fallback // that dropped it would ask a WIDER question than the batched read it // is standing in for — and for the caller that needs one, wider is not // "slower but the same": it is a different row. @@ -398,7 +398,7 @@ export async function buildExistingByName( */ organizationId?: string, /** - * [#11451] An extra EQUALITY predicate ANDed onto the `$in`, for a caller + * [commit c33f18592] An extra EQUALITY predicate ANDed onto the `$in`, for a caller * whose existence question is narrower than "a row with this name". * * `bootstrapSystemCapabilities`' curated half asks for the platform's OWN @@ -415,7 +415,7 @@ export async function buildExistingByName( * it. Narrowing can only SHRINK a page, so passing a predicate never makes * truncation likelier than the unpredicated read it replaces. * - * [#11518] That used to be a CORRECTNESS precondition the caller had to + * [commit e1d773eb7] That used to be a CORRECTNESS precondition the caller had to * discharge — an unscoped page was capped at `names.length`, so a * non-singleton question truncated, and a truncated page read as `absent`, * which INSERTS. {@link readNamePage} now measures its own truncation, so a @@ -423,7 +423,7 @@ export async function buildExistingByName( * and it says so) rather than a wrong answer. What the predicate still buys is * WHICH row answers: unscoped, the first row of the page is the row, so a * question wide enough to match somebody else's copy resolves to it — the - * separate harm #11451 exists for, and one no page budget can repair. + * separate harm commit c33f18592 closed, and one no page budget can repair. */ equals?: Readonly>, ): Promise { @@ -460,7 +460,7 @@ export async function buildExistingByName( // ⛔ NOT "none of them exist" — see the module header. Fall back to the // per-item read so behaviour is exactly what it was before the hoist. // - // [#11518] TWO events, ONE consequence. A truncated page is not a broken + // [commit e1d773eb7] TWO events, ONE consequence. A truncated page is not a broken // driver — the read worked and the answer is simply wider than one page — // so it is named separately, because the remedies differ: an unreadable // database is an outage, while a truncated page is an install whose diff --git a/packages/plugins/plugin-security/src/seed-write-refusal.test.ts b/packages/plugins/plugin-security/src/seed-write-refusal.test.ts index d76bdc2a036..ba870adb359 100644 --- a/packages/plugins/plugin-security/src/seed-write-refusal.test.ts +++ b/packages/plugins/plugin-security/src/seed-write-refusal.test.ts @@ -603,7 +603,7 @@ describe('a pass that is not refused reports exactly what it did before', () => * closed to new entries), and it refused this file when the pin was first * written that way. * - * [#13176] the sibling `tsconfig.test.json` compiles this file, so that + * [commit a68c61267] the sibling `tsconfig.test.json` compiles this file, so that * measurement no longer holds and a directive here WOULD be evaluated. The pin * stays a runtime assertion over the declaration's own AST anyway, and now for * its own reason rather than for the absent compiler: it reads OPTIONALITY off diff --git a/packages/plugins/plugin-security/src/share-link-tenant-wall.test.ts b/packages/plugins/plugin-security/src/share-link-tenant-wall.test.ts index b979cbd782e..bfbb26e3561 100644 --- a/packages/plugins/plugin-security/src/share-link-tenant-wall.test.ts +++ b/packages/plugins/plugin-security/src/share-link-tenant-wall.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#6206 / #6430 ruling A] The `group`-posture repro: minting a share link for + * [commit 8e13ca876 / #6430 ruling A] The `group`-posture repro: minting a share link for * a record the caller can read. * * ## Why this file lives in plugin-SECURITY diff --git a/packages/plugins/plugin-security/src/tenant-layer.test.ts b/packages/plugins/plugin-security/src/tenant-layer.test.ts index ea0f543d174..a292ffc3e42 100644 --- a/packages/plugins/plugin-security/src/tenant-layer.test.ts +++ b/packages/plugins/plugin-security/src/tenant-layer.test.ts @@ -221,7 +221,7 @@ describe('sys_api_key is not org-walled (#8287)', () => { }); /** - * [#8778] The stamp-only divergence must not move this object's Layer 0 + * [commit 7901b2dd2] The stamp-only divergence must not move this object's Layer 0 * inputs. `security-plugin.ts` derives them from exactly two reads — the * registered field set (`objectHasOrgIdField`) and * `tenancy.enabled === false || systemFields.tenant === false` diff --git a/packages/plugins/plugin-security/src/translations/index.ts b/packages/plugins/plugin-security/src/translations/index.ts index 62faa80b6c2..00dc38704e0 100644 --- a/packages/plugins/plugin-security/src/translations/index.ts +++ b/packages/plugins/plugin-security/src/translations/index.ts @@ -23,7 +23,7 @@ import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js'; * ## The provenance companions are READ here, not merely recorded * * `os i18n extract --source-hashes` writes `.source-hashes.generated.ts` - * beside these bundles (maintainer ruling #12069 Option A, #11671). A record + * beside these bundles (maintainer ruling #12069 Option A, commit 09b4f4e4e). A record * says: "this locale's leaf at that path is still a byte copy of THAT source * revision". Recording alone changes nothing a user sees — the substitution is * what {@link withSourceFallback} does, and until it was wired here this set From f90c9b123640c202cee3d9b2d16477e30433c054 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:39:31 +0000 Subject: [PATCH 2/2] chore(changeset): patch changeset for the plugin-security provenance re-anchor The rewritten docblocks ship in dist, so the package's published bytes change. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude --- .../20596-plugin-security-provenance-anchors.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/20596-plugin-security-provenance-anchors.md diff --git a/.changeset/20596-plugin-security-provenance-anchors.md b/.changeset/20596-plugin-security-provenance-anchors.md new file mode 100644 index 00000000000..737ec3418a9 --- /dev/null +++ b/.changeset/20596-plugin-security-provenance-anchors.md @@ -0,0 +1,11 @@ +--- +'@objectstack/plugin-security': patch +--- + +Provenance comments in `plugin-security` were re-anchored + +Comment and docblock lines under `src/` that cited tracker numbers which no +longer resolve on GitHub now cite the record in this repository that decided +the matter (an ADR where one exists, otherwise the commit in this repository's +history), and say in their own words what was decided. Comments only: no type, +schema, export, log or refusal text, or runtime behaviour changes.