Skip to content

docs(plugin-security): re-anchor the dead tracker citations to the commits and ADRs that decided them - #20658

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20596-plugin-security-citations
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20596-plugin-security-citations

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20596
Clause-②: no

What changed

This is the fourth stage of the domain:services lane of the dead-citation sweep. It covers packages/plugins/plugin-security/src/** and nothing else. By census it is the largest package in the lane; it waited while its own fixes were in flight, and the claim (5890784382) records that they have all landed. Later stages cover the other packages, so this PR says Part of and the card stays open.

Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on #19123), by the method of stages 1 to 3 (PR #20609 as 422db788a, PR #20626 as b80ab579d, PR #20634 as 4d04b6be3). That is 266 sites on 258 lines in 51 files, covering 40 numbers:

  • 140 census sites (all of this package's census sites except the 3 generated headers, see below);
  • 123 sites in test comments, which the census defers;
  • 3 sites in comment prose that the gate's extractor does not match at all: one hyphen-joined (#8919-era) and two slash-joined second numbers (#6483/#6608, #11184/#11343), see Acceptance notes.

Each rewritten line now cites the record in this repository that decided what the line describes, and says in its own words what was decided. Two numbers have an in-repo decision record, and it is preferred: #11082 cites ADR-0055's amendment (2026-09-07, transitive chains compose), and #6609 cites ADR-0094 D5-R, which records that conflict ruling (option A, accept the tightening). Every other number cites the commit in origin/main history that decided it: 36 distinct shas. Three pairs share one anchor because one number was the pull request that settled the other (#6483 and #6608, #16607 and #16722, #16608 and #16805); #12143 was itself a pull request, and its squash commit f64668d3c is also where route A (#11374) reached this plugin's key columns. No number was dropped.

Only comments changed. Every touched source file keeps its line count (266 lines out, 266 in, over 51 files), so no line citation into these files moves. 8 of those 266 lines hold no dead citation; they are reflow, listed under Wordings below. No code token moves (see the guard below).

No citation number is added. Every tracker number on an added line was already on the line it replaces. Over the whole diff, added minus removed is 0 or negative for every number (the gate's own extractCitations over the diff: 277 citations removed, 14 added, all 14 kept resolving numbers on the lines they already stood on), and no number is new to the diff. No PR number stands on an added line.

Sixty-five dead sites are left on purpose: 60 test strings, 2 operator log strings and 3 generated headers (see the list below).

One more file: a patch changeset for @objectstack/plugin-security, because the rewritten docblocks ship (see Changeset below).

Census: plugin-security, before and after

Instrument (A1). The gate's own node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count below is its allocated-but-absent findings under packages/plugins/plugin-security/. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run.

reading tree board whole-repo allocated-but-absent plugin-security sites lines files numbers
before base cd901d7a5, run 2026-09-29T13:00:53Z to 13:04:37Z enumerated, 185 pages, frontier #20647 (newest #20646 before, #20647 after), 18,474 numbers 1,707 143 140 22 28
after head aa067dad3, run 13:29:02Z to 13:32:37Z enumerated, 185 pages, frontier #20649 (newest #20649 before and after), 18,476 numbers 1,567 3 3 3 1

The before count matches the 143 that census 5884031174 read at f11b5f20. The 3 left are the generated #11671 headers. The whole-repo drop is 140, exactly this diff's census sites. The resolves tally is 32,878 in both runs, and resolves-as-pull-request (1,984) and cross-repo-unjudged (995) did not move either. The after run was taken on aa067dad3; the head f90c9b123 adds only the changeset. No run was truncated or discarded: all three enumerations in this stage (two census runs and the supplementary board below) read 185 pages at the newest frontier.

Supplementary instrument, the whole scope. The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported extractCitations (whole-file and comment-prose projections) and classifyCitation over every .ts file under plugin-security/src (216 files). It uses one board, enumerated by the gate's own enumerateBoard at 13:08:21Z (185 pages, frontier #20647, equal to the newest).

reading citations dead src comment test comment src string test string
before, cd901d7a5 2,746 327 143 123 2 59
after, aa067dad3 2,483 64 3 0 2 59

Its src-comment column equals the census's 143, which is the control on the second instrument. The 2,307 resolving, 88 pull-request and 24 cross-repo citations are the same in both readings. A third, raw reading (every # followed by digits, judged against the same board, whatever surrounds it) finds 331 dead occurrences before and 65 after: the 4 it sees beyond the gate are the three prose sites above and one more second number inside a kept test title.

Per-number table

Sites and files count every dead occurrence in scope at the base (comments and strings, tests included, gate-invisible spellings included). rewritten / left counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject.

number sites / files rewritten / left anchor: what it decided
#6206 2/1 1/1 8e13ca876: share-link enforcement takes the whole authz envelope (option-A ruling); it adds this package's group-posture repro. Stage 2's anchor
#6216 1/1 1/0 f586f1a89: one ExecutionContext assembler, with the closed-field-set pin. The anchor the spec, runtime and rest stages gave it
#6483 14/5 14/0 ee58392e1: ADR-0005's allow-list enforced, nine unapproved types (permission among them) rolled back to allowOrgOverride: false. Its message records the zero-row measurement, the allowRuntimeCreate boundary and this suite's stub blind spot. The spec stages' anchor
#6564 1/1 1/0 54299caad: the per-row ISharingService write verdict becomes tri-state (allow / abstain / deny); #6564 was that pull request
#6608 11/5 11/0 ee58392e1: #6608 was the pull request itself; this is its squash commit
#6609 3/2 3/0 ADR-0094 D5-R: the record of that conflict ruling (option A, accept the tightening), executed by #6858
#8692 10/3 9/1 712e185db: the 2026-08-15 ruling, option A: the seed insert stamps managed_by: 'platform' explicitly, forward only, and the resync skip warn stops claiming intent
#8714 15/2 10/5 42b05af89: explain reports a deactivated permission set or position through the shared held-state vocabulary. The anchor the spec stage gave it
#8757 14/3 10/4 6feac910b: the 2026-08-15 ruling: the master gate is the sole row-write authority for a controlled_by_parent detail; delegated writes keep both floors
#8772 5/2 5/0 8abada3ba: the freeze note, Direction 4 of the 2026-08-16 master-reference ruling; it names the two ramp legs and the three shapes this guard alone refuses
#8778 2/1 1/1 7901b2dd2: option A, a stamp-only, read-neutral tenancy.organizationField. The spec stage's anchor
#8804 2/1 2/0 db923a3a8: #8804 was the measurement pull request: a seeder-created row is stored 'admin', and resync reports resynced 0 / resyncSkipped 8
#8839 7/3 6/1 c25b2d52a: the 2026-08-15 ruling, reading 1: one per-object sys_comment delete policy, so moderation stops being dead behind the floor
#8865 14/2 12/2 498f4e884: the 2026-08-15 ruling, direction 1: leg 1 of the master gate drops the platform ownership floor on a sharing allow
#8919 1/1 1/0 b5378550e: /meta publish and rollback gated on manage_metadata; it created the write-door census whose count rule the line applies. The rest stage's anchor
#11082 18/2 13/5 ADR-0055's amendment (2026-09-07): controlled_by_parent composes across a chain, bounded, failing closed. One implementation-only line (the factory split) cites 61713314e, the commit that landed it
#11343 13/6 12/1 c0714eb5d: walled elevation requires a VERIFIED owner-email match, and the bootstrap replays on the verifying sys_user update. Stage 3's anchor
#11374 3/2 2/1 3954fb7df: route A, the 2026-08-24 ruling to declare a sourced maxLength on every keyed text column; the object-file line cites f64668d3c, which applied it to this plugin's key columns
#11451 20/4 18/2 c33f18592: the curated half's existence read becomes one batched $in carrying the #8470 predicate; the reconcile is equality-gated; the derived half's batching is filed, not decided
#11518 35/7 31/4 e1d773eb7: the unscoped existence page cap is measured, not trusted: one row more than the budget, and an overflowing page degrades loudly to the per-item read
#11520 17/2 15/2 1a6855226: the derived half is batched too, unnarrowed, on its own index; a derived name whose read cannot answer is declined
#11671 4/4 1/3 09b4f4e4e: generated translation leaves record the source revision they were filled from. Stages 1 and 2's anchor
#11702 1/1 0/1 a test title only; nothing to rewrite
#11703 15/3 13/2 5cb62d88b: clone_permission_set carries all five copied facets; the params list is the payload. The runtime stage's anchor
#11725 3/1 2/1 1e79aa4f8: the probe of the trash and restore door, which pinned its unreachability and measured the residual
#11753 2/2 2/0 0e4e51b0a: ActionParamSchema.carryOver, the carry-over ruling's schema half. The spec stage's anchor
#11843 5/4 4/1 5619aace3: the 2026-08-25 ruling, option B: the packaged-permission-set lock registered at the metadata door. The verbatim quotation 「11843 同意」 is kept as written
#12020 7/2 7/0 9cfc1f7e9: the lock extended to the restore leg, refusing on the durability channel; the residual tripwire inverted in the same change
#12143 2/1 2/0 f64668d3c: #12143 was the pull request itself: each plugin's keyed-text-bounds pin reads the widths off its own registration path
#12144 11/1 6/5 3a04b0125: the shared identifier schemas pinned to the storage columns that bound them; the ceiling is storage-owned
#12147 1/1 1/0 945e91a13: the class-level keyed-text-bounds gate over every *.object.ts, superseding the per-package pins
#13176 6/5 6/0 a68c61267: this package's test files put in front of tsc through the sibling tsconfig.test.json
#14484 2/1 1/1 3f64fe6c6: organization_id stamped on every sys_record_share write, with the backfill and the tenancy-ledger admission; it adds this test file. Stage 2's anchor
#16518 7/2 3/4 470746ae4: current_user.accessible_org_ids resolved into the RLS variable bag
#16607 8/3 4/4 1d73d45c1: RLS membership staged on the write check path, so a membership-keyed check resolves on a bare insert
#16608 13/4 6/7 a016f08b8: the insert-side RLS check judges the row that will be stored, after beforeInsert
#16682 22/4 18/4 9b9581b11: the single-posture promotion target is chosen, not sampled: the order stated to the driver, the declared owner preferred and required verified, bounded pages with a loud ceiling
#16722 1/1 1/0 1d73d45c1: #16722 was the pull request itself
#16805 1/1 1/0 a016f08b8: #16805 was the pull request itself; its message records the contract review's findings
#16861 7/2 6/1 1c83ca226: the already_have_admin guard stops letting the org-admin row count decide: two ordered, bounded legs that warn with the number examined
#19307 5/3 4/1 8f6d83147: the duplicate-name refusal on sys_permission_set carries UNIQUE_VIOLATION, and the packaged-set lock answers first. The spec stage's anchor

Every cited sha matches exactly one commit (git rev-parse --disambiguate, count 1 for each of the 36), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 36; the history is complete, --is-shallow-repository false, 15,092 commits). Where an earlier stage already anchored a number, this stage reuses that anchor after checking it against this package's lines.

Wordings to check

  • Two ADR anchors. #11082: ADR-0055's only amendment (2026-09-07) is the in-repo record of the chain decision, so the tags read [ADR-0055 amendment]; security-plugin.ts:8027 (the thrower split into a factory) is an implementation detail the ADR does not record, so it cites 61713314e. #6609: the lines already named ADR-0094 D5-R, and now say it records ruling A (permission-set-projection.ts:32, :535, permission-set-projection.test.ts:498).
  • A stale claim corrected, errors.ts:184-185. The line said the publish-time lint was 「open and unruled」. The ruling of 2026-08-16 made that false; 8abada3ba corrected the sibling paragraph in security-plugin.ts and missed this one. It now says the ruling (commit 8abada3ba) orders the lint ramp and that the ramp has not landed, which matches the security-plugin.ts paragraph (1 reflow line).
  • A vanished pull-request body, permission-set-projection.test.ts:14-16. The lines quoted the body of the pull request, which answers 404. They now state what ee58392e1's own message records about the same blind spot: this suite stubs saveMetaItem, and the real gate is pinned by the dogfood cases and a dedicated 403 suite (2 reflow lines).
  • The same, packaged-permission-set-restore-leg.test.ts:47. 「recorded on Extend the packaged-permission-set lock to the restore leg of the write-through — the one write point it does not guard #12020's PR」 became 「was measured for commit 9cfc1f7」; the line itself already states the measurement.
  • A referent, bootstrap-system-capabilities.test.ts:1148. 「this file's own The REST /meta publish and rollback doors carry no manage_metadata gate, so the authoring capability the PUT/DELETE doors enforce is reachable around #8919-era rule」: the count rule it applies lives in the write-door census that b5378550e created (the rule's text is bb920ee08's), not in this file. The line now says so.
  • Dead comment ids dropped with their issues. comment 5306089973 (security-plugin.ts:8093) and comment 5587754690 (bootstrap-platform-admin-walled-owner.test.ts:482). The verbatim maintainer quotation under the second is untouched.
  • Words where the anchor is one line away. bootstrap-platform-admin.ts:630 (「a pre-ruling install」, anchor on :628), bootstrap-platform-admin-walled-owner.test.ts:493 (「the TRIAGE seat's」, anchors on :463 and :482), security-plugin.ts:8137 (「that ruling」, anchor on :8132), identifier-storage-ceiling-pin.test.ts:51 (「the triage fence at the top of this file」, anchors on :13 and :25), packaged-permission-set-lock.test.ts:94 (anchor on :95).
  • Reflow, 8 lines with no dead site (every file keeps its line count): bootstrap-platform-admin.ts:267-268, errors.ts:185, identifier-storage-ceiling-pin.test.ts:26 (「dispatch」 became 「scope」, because the dispatch was the card's), packaged-permission-set-lock.test.ts:95, permission-set-projection.test.ts:15-16, security-plugin.ts:8094.
  • Box-drawing rulers. security-plugin.ts:3078 and bootstrap-platform-admin.ts:715, :1110, :1149 gave up as many trailing rule characters as the anchor added, keeping at least one.

The 65 sites left

  • Test titles, 57 sites. describe / it titles, which are string tokens, left as stages 1 to 3 left theirs: bootstrap-declared-capabilities.test.ts:454; bootstrap-platform-admin-existing-holder-scan.test.ts:297; bootstrap-platform-admin-promotion-selection.test.ts:281; bootstrap-platform-admin-seeded-provenance.test.ts:184; bootstrap-platform-admin-walled-owner.test.ts:504, :569, :587; bootstrap-seed-round-trips.test.ts:795 (two numbers), :980; bootstrap-system-capabilities.test.ts:968, :1096; controlled-by-parent-chain.test.ts:460, :540, :578; controlled-by-parent-detail-write-authority.test.ts:594, :650, :669, :708, :724, :813; explain-engine.test.ts:171, :203, :853, :873, :893; identifier-storage-ceiling-pin.test.ts:125, :143, :160; insert-check-post-image.test.ts:573, :612, :662, :775, :838, :875, :939; objects/default-permission-sets.test.ts:299; packaged-permission-set-lock-gate.test.ts:183; packaged-permission-set-lock.test.ts:647, :812, :813; packaged-permission-set-restore-leg.test.ts:264, :265; permission-set-duplicate-name-refusal.test.ts:195; plugin-keyed-text-bounds.test.ts:67; record-share-tenant-wall.test.ts:149; rls-accessible-org-ids-plumbing.test.ts:191, :256, :325, :382; rls-check-membership-staging.test.ts:388, :400, :439, :505; security-plugin.test.ts:153; share-link-tenant-wall.test.ts:239; tenant-layer.test.ts:237.
  • Test assertion messages, 3 sites. String literals passed to expect: identifier-storage-ceiling-pin.test.ts:172, :193 (#12144) and packaged-permission-set-lock.test.ts:694 (#11703).
  • Operator log strings, 2 sites. security-plugin.ts:7864 and :7872, the two logger.error lines of the chain guards (#11082). Runtime strings are form D, and the shrink-only doc-authoring-prose-id baseline already holds both (security-plugin.ts, #11082: 2).
  • Generated headers, 3 sites. translations/{es-ES,ja-JP,zh-CN}.source-hashes.generated.ts:8 (#11671). Their producer is a string literal in packages/cli, outside this lane; the pointer is on dead tracker citations in the domain:cli packages (689 sites, 166 numbers, 95 files): the ruling C+D stage for this lane (from #20556) #20594.
  • There is no quoted ruling carrying a dead number in this package: the one verbatim quotation, 「11843 同意」, carries no #.

Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes excluded, base cd901d7a5 against head. Template literals are therefore read in context. It ran over all 51 touched .ts files.

  • Real run: 221,086 base tokens, 0 files with a token change (exit 0).
  • Comment control in seed-name-lookup.ts (TWO events, ONE consequence to TWO events, ONE result): 0 files changed, as expected (exit 0).
  • Positive control, a code token added in seed-name-lookup.ts (an extra key in the batched read's where): DIFFER (exit 1).
  • Positive control, one digit changed inside a kept test title (bootstrap-system-capabilities.test.ts:1096): DIFFER (exit 1).

Every mutation went through scripts/ablation-replace.mjs, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (36e6be731e6a, e1f66feaa6fc), with git diff HEAD empty and a clean tree afterwards.

Changeset

This change ships bytes, so a patch changeset for @objectstack/plugin-security (.changeset/20596-plugin-security-provenance-anchors.md) is included. It says only that the provenance comments were re-anchored.

Measured on the built package (A3): files[] is dist, README.md and CHANGELOG.md. After the build, the rewritten comments reach dist: ADR-0055 amendment appears 4 times in each of dist/index.d.ts, index.d.mts, index.js and index.mjs; 6feac910b, 498f4e884 twice in each of the four; c0714eb5d, e1d773eb7, db923a3a8, 470746ae4, 1d73d45c1, 9b9581b11 once in each of the four; ee58392e1 3 times and 1c83ca226 twice in each declaration file; 5cb62d88b 4 times and c25b2d52a 3 times in each runtime file. Positive control: the unchanged line 「declared the key's SHAPE」 beside a shipped rewrite (rls-compiler.ts:88-89) is found once in index.d.ts, beside 「Until commit 470746a nobody did」. A never-written negative phrase appears nowhere. The only dead numbers left in dist are the two kept #11082 log strings in the runtime files.

Gates (head f90c9b123)

  • Citation judging, as CI runs it: pnpm check:issue-citations (self-test) exits 0. node scripts/check-issue-citations.mjs exits 0: the diff-scoped run judged 9 citations across 19 files, and all 9 resolve.
  • Doc authoring: pnpm check:doc-authoring exits 0, with the sibling-package prose ids at their baseline and no growth.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at f90c9b123 derived 66 commands: all 57 derived at dispatch, plus check:duration-unit-keys, check:dispatcher-error-vocabulary, check:engine-double-contract, check:logger-receiver-detach, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher. It was re-derived after a fresh git fetch (origin/main c6b37cd08, 3 commits ahead): the same 66. Each ran with its exit code captured before any pipe, and all 66 exit 0. --ran, fed each command with its exit code, reports 66 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full turbo run build of ./packages/* and ./packages/*/* ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace.
  • Roster families the derivation lists outside its commands (their rosters sit in directories this diff touches): node scripts/check-changeset-fixed.mjs, pnpm check:error-code-casing and pnpm check:filter-alias-parity, each exit 0.
  • Tests and typecheck, under the verify lock:
    • pnpm --filter @objectstack/plugin-security test: 147 files pass, 3,202 tests pass and 23 skip. That is every test file in the package, the 32 touched ones included.
    • pnpm --filter @objectstack/plugin-security typecheck exits 0 (tsc main, tsconfig.scripts.json, and check:test-typecheck at zero). The main program reads 69 non-test files; the tsconfig.test.json program reads all 216 files under src/, the 147 test files included, and all 51 touched files are in it (--listFiles).
  • Lint, as a proven narrowing: eslint --no-inline-config --format json over the 51 touched .ts files gives 51 files, 0 errors and 0 warnings. All 51 are in eslint's own population (isPathIgnored is false for each). eslint.config.mjs never enables type-aware linting (no parserOptions.project, as its own line 328 states), so a comment edit here cannot move the verdict on any untouched file. The repo-wide pnpm lint is CI's run.
  • Control bytes: pnpm check:nul-bytes exits 0, and a raw scan of the 52 changed files for control bytes finds none.

Acceptance notes

  • The gate's extractor does not see a number after a slash either. CITATION_RE opens with a lookbehind that refuses a / before the # (scripts/check-issue-citations.mjs:449), so in #A/#B only #A is a citation to the diff gate and the census, dead or alive. It is the same blind-spot family as the hyphen spelling (check-issue-citations closeout (extractor spellings): CITATION_RE refuses a hyphen after the digits, so a dead #N-word citation (#13398-class) is invisible to the diff gate and to the census #20636). This stage rewrote the two such prose sites in plugin-security (permission-set-overlay-discard.ts:25, platform-owner-wall-bypass.ts:69) because they are the same dead numbers in the same comment prose. A raw scan of packages/**/src .ts files against the board finds 33 dead second numbers of this shape at the base and 31 at the head (one of them the kept title bootstrap-seed-round-trips.test.ts:795), in 14 packages. The census cannot count them, so a later stage has to look for them by hand. No instrument change here.
  • The hyphen spelling in this package (check-issue-citations closeout (extractor spellings): CITATION_RE refuses a hyphen after the digits, so a dead #N-word citation (#13398-class) is invisible to the diff gate and to the census #20636 names 1 here on main) was bootstrap-system-capabilities.test.ts:1148, rewritten. 9 dead #N-word sites remain in packages/**/src at the head, none in this package.
  • The census instrument did not truncate in this stage. Three enumerations read 185 pages each at the newest frontier.
  • Anchors the next stages can reuse. These numbers stand elsewhere on the census at the head: #11374 in drivers (16), platform-objects (14) and plugin-audit (2), anchor 3954fb7df (route A); #6216 in core (8), mcp (1) and plugin-hono-server (1), anchor f586f1a89; #6483 (8) and #6608 (4) in metadata-protocol, anchor ee58392e1; #6206 in core (2), plugin-approvals (3), plugin-audit (1) and service-storage (1), anchor 8e13ca876; #8778 in metadata-core, plugin-approvals and service-storage, anchor 7901b2dd2; #16608 (4) and #16805 (2) in objectql, anchor a016f08b8; #11343 in types (2), anchor c0714eb5d; #8692 in cli (2), anchor 712e185db; #12144 in metadata-protocol (1), anchor 3a04b0125; #16682 in core (1), anchor 9b9581b11.
  • Base. The branch is 3 commits behind origin/main (c6b37cd08, read at 13:54Z). None touches plugin-security or any of these numbers; they add three unrelated changesets and move one row of scripts/doc-authoring-prose-id.baseline.json (a packages/lint entry), so there was no merge.

Generated by Claude Code

…mmits 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 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 24 documentable anchor(s). ⚠️ 9 changed file(s) yielded no anchor (packages/plugins/plugin-security/src/errors.ts, packages/plugins/plugin-security/src/index.ts, packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/access-recipes.mdx (via rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))
  • content/docs/permissions/field-level-security.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/permissions/index.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/permissions/permission-metadata.mdx (via rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))
  • content/docs/permissions/permissions-matrix.mdx (via computeLayeredRlsFilter (symbol, a method of class SecurityPlugin))
  • content/docs/permissions/rls.mdx (via rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))
  • content/docs/permissions/sharing-rules.mdx (via computeLayeredRlsFilter (symbol, a method of class SecurityPlugin), rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))
  • content/docs/permissions/system-context.mdx (via createPermissionSetWriteThrough (symbol, a top-level function))
  • content/docs/plugins/packages.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/protocol/objectql/security.mdx (via rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))
  • content/docs/ui/forms.mdx (via SecurityPlugin (symbol, a top-level class))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via RLSUserContext (symbol, a top-level interface), SecurityPlugin (symbol, a top-level class), rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))
  • content/docs/releases/v17/17-5.mdx (via rowLevelSecurity (symbol, a field of const object baseDefaultPermissionSets))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 9 changed file(s) yielded no anchor (packages/plugins/plugin-security/src/errors.ts, packages/plugins/plugin-security/src/index.ts, packages/plugins/plugin-security/src/packaged-permission-set-lock-gate.ts, …) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9b402dbaed683bcbc9c273bb6792af4adc02744c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2e0daa0b36b2c61f01868b64444f32db7926d56a — the merge of head f90c9b123640c202cee3d9b2d16477e30433c054 into base 9b402dbaed683bcbc9c273bb6792af4adc02744c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2e0daa0b36b2c61f01868b64444f32db7926d56a && git checkout 2e0daa0b36b2c61f01868b64444f32db7926d56a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9b402dbaed683bcbc9c273bb6792af4adc02744c f90c9b123640c202cee3d9b2d16477e30433c054 && git checkout -B drift-repro 9b402dbaed683bcbc9c273bb6792af4adc02744c && git merge --no-ff f90c9b123640c202cee3d9b2d16477e30433c054

node scripts/docs-audit/affected-docs.mjs --json 9b402dbaed683bcbc9c273bb6792af4adc02744c

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 9b402dbaed683bcbc9c273bb6792af4adc02744c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 15:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 9a4b2bb Sep 29, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20596-plugin-security-citations branch September 29, 2026 16:17
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…bject leaves that contradict their current en source (objectstack-ai#20684)

Fixes objectstack-ai#20653

Clause-②: no

## What changed

A translated leaf in a plugin's
`src/translations/{ja-JP,es-ES,zh-CN}.objects.generated.ts` that a
translator wrote by hand keeps its value when its `en` source changes
later. This PR measures that set in the four plugin bundles the card
names (`plugin-webhooks`, `plugin-audit`, `plugin-approvals`,
`plugin-security`), then re-translates the leaves whose meaning now
**contradicts** the current `en`: **18 leaves**, six paths in all three
locales (ja-JP 6, es-ES 6, zh-CN 6). The triage-ordered leaf,
`sys_webhook.fields.definition_json.help`, is the first commit.

- Values only. No key is added or dropped, no `en` file is edited, and
no provenance companion (`*.source-hashes.generated.ts`) changes: the
repo's own tooling was run and writes nothing (measured below).
- `.changeset/20653-stale-authored-plugin-bundle-leaves.md`:
`@objectstack/plugin-webhooks`, `@objectstack/plugin-audit`,
`@objectstack/plugin-security`, each `patch`.
`@objectstack/plugin-approvals` has no change and is not named.
- No new gate, per triage. No test pins any of the old or new strings.
- Scope per the claim: the four plugin bundles only. The
`platform-objects` metadata-forms leaves are the engine lane's card,
objectstack-ai#20666, which remains open and is not touched here.

## The measurement (base `6bff748bb`, before any edit)

### Instrument

PR objectstack-ai#20652's condensed instrument, ported with one change: the bundle
directory comes from argv instead of the `platform-objects` constant.
Population = every string leaf of the locale's objects bundle, minus
echoes of the current `en` and paths recorded in
`*.source-hashes.generated.ts`; last edit = the first-parent commit
where the parsed value last changed; a leaf is a candidate when `en` at
that commit differs from `en` today. Meaning is then judged by hand.
Full history (the clone is not shallow: `git rev-parse
--is-shallow-repository` answers `false`).

Four widening checks ran beside it:

- **Carve-out continuity.** These bundles were carved out of
`packages/platform-objects/src/apps/translations/` at `44045721c`
(ADR-0029 D8: webhooks, approvals, security) and `be1b9165f` (K2:
audit). The condensed walk starts at the carve-out, so it dates every
carried leaf there and reads the `en` of that day. The widened walk
replays the platform-objects file's first-parent history (from its birth
at `6bacbced2`, the horizon objectstack-ai#20652 covered) before the plugin file's
own. It re-dated every candidate the condensed walk had dated at a
carve-out commit (for example `definition_json.help`, to `7bb92056e`)
and changed no candidate set.
- **Merge side.** For a leaf last edited by a merge commit, `en` is also
read at the second parent. 0 hits.
- **Penultimate edit.** For each non-candidate, `en` at its previous
edit, and whether `en` moved in the last-edit commit itself. Every flag
where `en` moved EARLIER than the last edit was read by hand:
`sys_webhook.fields.method.help`, `sys_position.fields.name.help` and
`sys_position._actions.clone_position.params.name.helpText` already say
what `en` says; es-ES
`sys_position._actions.deactivate_position.confirmText` differs by
rewording only; es-ES `sys_position.description` **contradicts** (it was
edited at `4ea921ca8`, after `en` moved, and kept the old framing), so
it is re-translated.
- **Retired terms and renamed keys.** A leaf carrying the locale's word
for `project` or `department` where `en` at the same path does not carry
the English term; the locale's word for `role` or `RBAC` anywhere in the
four bundles; and a leaf whose path was born at its last edit while the
same value sat under a sibling path of the same object that disappeared
in that commit (a renamed key). One path, found by both the term scan
and the renamed-key check: `sys_activity.fields.environment_id.help`.
The key was renamed from `project_id` at `944f18758` with the value
carried, so the since-last-edit instrument dates it at the rename, where
`en` already said Environment, and cannot see it.

### Known-positive control

`plugin-webhooks` `sys_webhook.fields.definition_json.help`, line 72 of
all four bundles. The source has said "Credentials are NOT stored here"
since `160294963` (the custom headers moved onto the encrypted channel,
after `e3a6f6e7e` moved the signing secret), and the `en` bundle since
`8af76aebf`. The three translations were last edited at `7bb92056e`,
when `en` said "full headers/auth/retry/payload config". The ported
instrument flags it in **all three** locales. A1 held: the port is live
before anything is counted.

### Counts per bundle (ja-JP / es-ES / zh-CN)

| bundle | authored leaves | candidates, before | contradicting, before
| widening checks, before | **re-translated** | candidates, after |
contradicting after | widening after |
|---|---|---|---|---|---|---|---|---|
| plugin-webhooks | 41 / 40 / 40 | 3 / 3 / 3 | 2 / 2 / 2 | 0 / 0 / 0 |
**2 / 2 / 2** | 1 / 1 / 1 | 0 | 0 |
| plugin-audit | 102 / 100 / 102 | 3 / 3 / 3 | 2 / 2 / 2 | 1 / 1 / 1
(`environment_id.help`) | **3 / 3 / 3** | 1 / 1 / 1 | 0 | 0 |
| plugin-approvals | 113 / 112 / 134 | 17 / 17 / 16 | 0 / 0 / 0 | 0 / 0
/ 0 | **0** | 17 / 17 / 16 | 0 | 0 |
| plugin-security | 179 / 179 / 179 | 7 / 5 / 7 | 1 / 0 / 1 | 0 / 1 / 0
(es-ES `sys_position.description`) | **1 / 1 / 1** | 6 / 5 / 6 | 0 | 0 |

The before candidate counts equal the objectstack-ai#20539 dev's raw counts exactly
(A2). After = at `5444bb130`, both instruments: each after-candidate set
is exactly the before set minus the re-translated paths (0 added), the
echo counts are unchanged (no new value equals its `en` leaf), and the
term scan and renamed-key check return 0. The penultimate-edit flags
after are the before set minus es-ES `sys_position.description`, plus
the 14 re-translated leaves that were candidates (their last edit is now
this PR, after `en` moved), which is the expected shape.

### The judgment rule

As in PR objectstack-ai#20652. A leaf **contradicts** the current `en` when a reader
who acts on it would believe something about the current field or object
that `en` now says is false: `en` now denies what the leaf asserts; the
object was re-modelled, so the leaf describes a different thing; or the
leaf names the record's entity by a term a rename retired from it. Added
detail, narrowing, rewording, punctuation and title-casing are not
contradictions.

## Re-translated (18): before and after, with the `en` each now matches

(ja-JP / es-ES / zh-CN, in each bundle's existing terms: 署名シークレット /
カスタムヘッダー / アウトボックス / ケイパビリティ; secreto de firma / cabeceras
personalizadas / outbox / capacidades / entorno; 签名密钥 / 自定义请求头 / 发件箱 /
授权能力 / 环境.)

**`plugin-webhooks` · `sys_webhook.fields.definition_json.help`**: said
the JSON carries the full headers / auth / retry / payload config. `en`
says the opposite, on a security field. `en`: 「Serialised Webhook JSON
(see @objectstack/spec/automation/webhook) — timeout and the rest of the
authored envelope. Credentials are NOT stored here: the signing secret
lives in the encrypted `signing_secret` field and the custom headers in
the encrypted `headers_secret` field.」

- ja-JP: 「シリアライズされた Webhook JSON(@objectstack/spec/automation/webhook
参照)— ヘッダー/認証/リトライ/ペイロード設定を含む」 → 「シリアライズされた Webhook
JSON(@objectstack/spec/automation/webhook 参照)—
タイムアウトなど、作成されたエンベロープの残りの設定。認証情報はここには保存されません。署名シークレットは暗号化された
`signing_secret` フィールドに、カスタムヘッダーは暗号化された `headers_secret` フィールドに保存されます。」
- es-ES: 「JSON serializado de Webhook (consulte
@objectstack/spec/automation/webhook): configuración completa de
cabeceras/auth/reintentos/payload.」 → 「JSON serializado de Webhook
(consulte @objectstack/spec/automation/webhook): el tiempo de espera y
el resto del envelope definido. Las credenciales NO se almacenan aquí:
el secreto de firma se guarda en el campo cifrado `signing_secret` y las
cabeceras personalizadas, en el campo cifrado `headers_secret`.」
- zh-CN: 「序列化的 Webhook JSON(参见
@objectstack/spec/automation/webhook)——包含完整的 headers/auth/retry/payload
配置」 → 「序列化的 Webhook JSON(参见
@objectstack/spec/automation/webhook)——超时及所编写信封的其余配置。凭据不存储在此处:签名密钥保存在加密的
`signing_secret` 字段中,自定义请求头保存在加密的 `headers_secret` 字段中。」

**`plugin-webhooks` · `sys_webhook.description`**: said an HTTP
connector plugin executes the webhook. `69f1dfd5c` replaced that
executor in the source with the webhook auto-enqueuer and the shared
HTTP outbox (`service-messaging`), and no HTTP connector plugin exists
in the tree (`packages/plugins`, `packages/services`). The leaf also
omitted the `defineStack({ webhooks })` door, which is additive. `en`:
`Outbound HTTP webhook subscription. Declared in code via defineStack({
webhooks }) / defineWebhook() (materialized into rows on boot) or
authored directly in the Studio editor; dispatched by the webhook
auto-enqueuer onto the shared HTTP outbox.`

- ja-JP: 「送信 HTTP Webhook サブスクリプション。defineWebhook() またはスタジオエディタで作成し、HTTP
コネクタプラグインが実行します。」 → 「送信 HTTP Webhook サブスクリプション。コードでは defineStack({
webhooks }) / defineWebhook()
で宣言する(起動時に行として実体化)か、スタジオエディタで直接作成します。Webhook 自動エンキューアが共有 HTTP
アウトボックスへディスパッチします。」
- es-ES: 「Suscripción saliente de Webhook HTTP. Se crea mediante
defineWebhook() en código o con el editor de Studio; la ejecuta el
plugin del conector HTTP.」 → 「Suscripción saliente de Webhook HTTP. Se
declara en código mediante defineStack({ webhooks }) / defineWebhook()
(materializada en filas al arrancar) o se crea directamente con el
editor de Studio; el encolador automático de webhooks la despacha al
outbox HTTP compartido.」
- zh-CN: 「外发 HTTP Webhook 订阅。可在代码中通过 defineWebhook() 编写,或在 Studio
编辑器中维护;由 HTTP 连接器插件执行。」 → 「外发 HTTP Webhook 订阅。可在代码中通过 defineStack({
webhooks }) / defineWebhook() 声明(启动时物化为数据行),或直接在 Studio 编辑器中编写;由 Webhook
自动入队器分发到共享的 HTTP 发件箱。」

**`plugin-audit` · `sys_activity.fields.environment_id.label`**: the
v5.0 rename project to environment ships no alias. `en`: `Environment`

- ja-JP: 「プロジェクト」 → 「環境」
- es-ES: 「Proyecto」 → 「Entorno」
- zh-CN: 「项目」 → 「环境」

**`plugin-audit` · `sys_activity.fields.environment_id.help`**: same
rename; found by the term scan and the renamed-key check, invisible to
the since-last-edit instrument. `en`: `Environment context
(multi-environment deployments)`

- ja-JP: 「プロジェクトコンテキスト(マルチプロジェクトデプロイメント)」 → 「環境コンテキスト(マルチ環境デプロイメント)」
- es-ES: 「Contexto del proyecto (implementaciones multiproyecto).」 →
「Contexto del entorno (implementaciones multientorno).」
- zh-CN: 「项目上下文(多项目部署)」 → 「环境上下文(多环境部署)」

**`plugin-audit` · `sys_audit_log.fields.user_id.label`**: `7fe7e85d6`
gave `sys_audit_log` its own `actor` principal field (label Actor;
ADR-0014 D2) and relabelled the strict `sys_user` lookup `user_id` from
Actor to User, because a service-token action leaves `user_id` null and
records the principal in `actor`. The leaves still called `user_id` the
actor: es-ES showed two fields both labelled 「Actor」, and ja-JP (操作者
beside 実行者) and zh-CN (执行人 beside 操作者) showed two synonyms. `en`: `User`

- ja-JP: 「操作者」 → 「ユーザー」
- es-ES: 「Actor」 → 「Usuario」
- zh-CN: 「执行人」 → 「用户」

**`plugin-security` · `sys_position.description`**: the ADR-0090 P1
commit (`6d83431cf`) changed `en` from "Role definitions for RBAC access
control" to capability distribution; the translations were find-replaced
role to position and kept "for RBAC access control" (ADR-0090:
capability = `permission_set`, distribution = `position`, and the word
role is retired from UI copy). `en`: `Position definitions for
capability distribution (ADR-0090)`

- ja-JP: 「RBAC アクセス制御のためのポジション定義」 → 「ケイパビリティ配分のためのポジション定義(ADR-0090)」
- es-ES: 「Definiciones de puesto para el control de acceso RBAC」 →
「Definiciones de puesto para la distribución de capacidades (ADR-0090)」
- zh-CN: 「用于 RBAC 访问控制的岗位定义」 → 「用于分发授权能力的岗位定义(ADR-0090)」

The two closest calls on this side are `sys_webhook.description` and
`sys_audit_log.fields.user_id.label`; the reasons are above.

## Stale but not contradicting (listed, left as written)

| bundle · path | locales | what `en` did |
|---|---|---|
| webhooks · `sys_webhook.fields.triggers.help` | all three | Condensed
to "(bulk_* deliver a count, not a record)" when the `en` bundle began
tracking its source (`8af76aebf`). The leaf's longer "bulk_update /
bulk_delete fire on predicate writes and deliver a count" is still true
(the field's own source comment says the same). |
| audit · `sys_audit_log.fields.user_id.help` | all three | Widened
"null for system actions" to "null for non-user / service actions — see
actor". The leaf is narrower, not false. |
| approvals · `sys_approval_request.fields.status.options.*` (5),
`sys_approval_action.fields.action.options.*` (12) | 17 / 17 / 16 |
Moved from the machine value to a title (`pending` to `Pending`,
`request_info` to `Request Info`, `ooo_substitute` to `Out-of-Office
Substitution`). Every leaf already carried the meaning. Triage: not a
contradiction. |
| security · `sys_position.fields.managed_by.help`,
`sys_capability.fields.managed_by.help`,
`sys_permission_set.fields.managed_by.help` | all three | Added the
unified tri-state wording and the legacy aliases, or reworded. The
leaves' platform / package / admin reading is still true. |
| security · `sys_capability.description` | all three | Added the
ADR-0066 citation and named the two referencing keys. |
| security · `sys_position._actions.deactivate_position.confirmText` |
ja-JP, zh-CN (es-ES via the penultimate check) | Dropped "with the
position". |
| security · `sys_permission_set.fields.name.help` | all three | Added
that the name is the set's metadata identity and cannot be renamed
(clone instead). The leaf does not claim it can. This is the closest
call among the leaves left alone. |

## Dispatch hypotheses, measured

- **A1 held.** The ported instrument flags the known positive in all
three locales, and its counts at `6bff748bb` are 3/3/3, 3/3/3, 17/17/16
and 7/5/7.
- **A2 held as a lead.** The raw counts reproduce exactly. Meaning
judged per candidate: 5 of the 30 / 28 / 29 candidates contradict in
ja-JP / zh-CN, 4 in es-ES, plus one term-scan leaf per locale and one
penultimate-edit leaf in es-ES. The named likely positive
(`sys_activity.fields.environment_id.label`) is a positive in all three
locales (es-ES 「Proyecto」 too).
- **A3 held: values only.** `node scripts/check-i18n-bundles.mjs --write
--filter=plugin-NAME` for webhooks, audit, security and approvals
printed `regenerated` for each; the files were rewritten on disk and
`git status --porcelain` stayed empty, so the tooling owes no companion
or `en` change. Control (a commit-first ablation through
`scripts/ablation-replace.mjs`): with ja-JP
`sys_webhook.fields.definition_json.help` set to the `en` string,
`check-i18n-bundles --filter=plugin-webhooks` goes red,
`plugins/plugin-webhooks DRIFTED (1)`; the tool restored it (blob
`88583c2ea5f9` == HEAD, `git diff HEAD` empty).
- **A4 held.** Each changed plugin ships the new values in `dist` (built
at `5444bb130`). `plugin-webhooks`: the built chunk
`dist/translations-KQ72WOMS.js`, the module the plugin's `kernel:ready`
hook imports, was imported and its served value (after
`withSourceFallback`) read at each path: 6 of 6 equal the HEAD source
and differ from the base, and 3 unchanged-leaf controls equal.
`plugin-audit` and `plugin-security` inline their bundles: each locale's
object literal was cut out of `dist/index.mjs` and `dist/index.js` and
read by path: 24 of 24 rows equal the HEAD source and differ from the
base, and 12 controls equal. A plain byte grep was not a usable reading
here: esbuild escapes non-ASCII text, and the old short labels are
shared by other fields.

## Verification (all at `5444bb130`, after merging `origin/main` at
`9a4b2bb38`)

The merge brought PR objectstack-ai#20658, a comment-only edit that includes
`plugin-security/src/translations/index.ts`, a file this PR does not
edit.

- Build: `turbo run build --filter=@objectstack/cli...
--filter=@objectstack/plugin-webhooks...
--filter=@objectstack/plugin-audit...
--filter=@objectstack/plugin-security...`, 59/59 tasks, `VERDICT
command-exit 0`.
- `pnpm --filter` test, for the three changed plugins: plugin-webhooks
13 files / 160 tests passed; plugin-audit 25 files / 363 tests passed;
plugin-security 147 files / 3202 passed, 23 skipped (the translation
tests `bundle-ownership.test.ts` and
`position-rename-consistency.test.ts` included). `typecheck` for the
three: exit 0, each `check:test-typecheck: OK`.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands` (no paths)
derived 57 commands against the real diff; the dispatch-time list had
56, and the one added is `pnpm check:logger-receiver-detach`. All 57
exit 0. `--ran` printed "57 derived famil(ies) accounted for — 57 run, 0
NOT-MEASURED". `pnpm check:dual-build-cjs-loads` first refused with exit
3 (nine packages outside this diff had no `dist/`). Those were built and
the gate re-ran: exit 0.
- The four roster families printed outside the runnable list: `node
scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm
check:error-code-casing`, `pnpm check:filter-alias-parity`, all exit 0.
- Named in the verdicts: `check:i18n` "OK (9 package(s) — all bundles in
sync, no undeclared authoring keys)"; `check:i18n-stale-fill` "OK (10
bundle set(s) — no new stale fills, 0 baselined)"; `check:nul-bytes` OK.
- Lint, narrowed to the nine edited bundles: `eslint --no-inline-config
--format json` read 9 files, 0 errors, 0 warnings.
`ESLint#isPathIgnored` answers `false` for all nine, read from the
repo's own config. The config enables no type-aware linting (no
`parserOptions.project`; `eslint.config.mjs` states it at lines
327-328), so a change to string values in these files cannot move the
verdict on any other file. The full `pnpm lint` is left to CI.

## Acceptance notes

- `position-rename-consistency.test.ts` matches the retired term as
`\brole?s?\b` / `\brol(es)?\b` / 角色 / ロール. None of those matches the
acronym RBAC, which is how `sys_position.description` kept "for RBAC
access control" in all three locales under a green ADR-0090 guard. This
PR fixes the values; the test's blind spot remains. It is outside this
card's file surface and is not changed here.
- The since-last-edit instrument cannot see a leaf whose KEY was renamed
with its value carried (here `project_id` to `environment_id`). The
renamed-key check above covers that shape. objectstack-ai#20666 runs the same
instrument on the metadata-forms bundles and may want the same check.
- Instrument sources and outputs were run from the session scratchpad;
the widened instrument is the condensed one plus the four checks
described above.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants