Skip to content

fix(verify, plugin-dev, plugin-hono-server): the verify --rls report, tenancy refusals and no-API warning state each decision in words instead of a tracker number (stage 4) - #21231

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20752-stage4-verify-plugin-strings
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20752-stage4-verify-plugin-strings

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20752
Clause-②: no

Stage 4 of 5 of the domain:cli lane under the maintainer's A / A ruling (5902360492): the packages/verify, plugin-dev and plugin-hono-server strings. The card stays open for stage 5 (qa), so this PR carries no closing keyword. Text only: no status, error code, exit code, route, field, export, control flow, or verify verdict or count moves.

What this does

The objectstack verify --rls report, its persona-provisioning refusals and the records its probe writes, the verify harness's organizations remedy, the dev plugin's tenancy refusals and no-auth warning, and the Hono server's no-API warning sent the reader to a tracker number for the reason behind them. In form D, as stages 1 to 3 applied it (PR #21172, PR #21188, PR #21219), the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words.

All 14 ledgered occurrences in the claim's four files (claim 5938321786), at 13 string sites: rls.ts 9 (1994 x3, 7685 x3, 7978 x3), dev-plugin.ts 3 (4818 x2, 3963 x1), harness.ts 1 (4719), hono-plugin.ts 1 (4073). Every cited card was read first; all seven answer, and each decision was cross-read against its landing commit.

Rewritten in words

Where (head line) Cited The text now says Decision read from
rls.ts:908 report header 1994 === objectstack verify (RLS / cross-owner by-id-write invariant) — APP === card 1994 (a by-id update or delete must pass the row-level write filter: a member can no longer change a record it cannot see), and this module's header, which names that invariant
rls.ts:768 rls-hole detail 1994 "...by-id write bypassed RLS, and a caller that cannot read a record must not be able to write it" card 1994; the module header's "A user who CANNOT READ a record must not be able to WRITE it"
rls.ts:431 probe-persona refusal 1994 "...masked by the object gate and a by-id write that bypasses RLS is unreachable." card 1994 for the class; the 7685 half is citation-only (below)
rls.ts:943 position-persona report line 7978 ── position personas (each holds one declared position and nothing else) — N of M declared position(s) probed card 7978 and landing commit f5434b0ea4 (one persona per declared position, each holding that position and nothing else)
dev-plugin.ts:934-935 no-auth warning 3963 "...anonymous access to object data is always denied, with no setting that turns that off." card 3963 decision A1 and landing commit 3c628ce647 (the api.requireAuth opt-out is retired; anonymous data access is denied unconditionally); stage 1's twin wording in os serve

Citation only (the sentence already stated the decision)

  • rls.ts:357 probe RLS policy description and rls.ts:464 probe sys_permission_set description (7685): each already says the probe holds object read+edit plus an owner-scoped narrowing so a refusal is the record gate's. Read from card 7685 item (i) and landing commit be37f859bc. The 7685 citation at rls.ts:431 goes the same way.
  • rls.ts:553 position-persona refusal and rls.ts:575 sys_user_position reason (7978): each already says position-gated policies only apply to a persona holding the position.
  • dev-plugin.ts:874 construct-stage refusal and dev-plugin.ts:1000 init-stage refusal (4818): each already says OS_ALLOW_DEGRADED_TENANCY covers an absent multi-org runtime, not a present one that declined. Read from card 4818 and landing commit 29326f8eea; stage 1 dropped the twin citation in os serve the same way.
  • harness.ts:626 bootStack remedy (4719): it already says the app's declaration is what is checked and that a package reachable only through NODE_PATH or a hoisted store is not accepted. Read from card 4719 and landing commit 02dc076927.
  • hono-plugin.ts:683 no-API boot warning (4073): it already says the plugin is a transport adapter that serves neither API. Read from card 4073 and landing commit e5a4d26901.

The rls.ts report header was on the ledger, so it is rewritten to name the invariant it proves rather than losing its anchor.

Text only, proven on the AST

A scratch script (not committed) parses each changed TypeScript file at BASE 7c5a311a58 and at head 36281b515a, folds every + chain made only of string literals into one value, blanks every string value and template span, and compares the remaining node sequence (kinds, identifiers, numerals). Result: identical skeleton in all six files, string-value counts equal (dev-plugin 220, hono-plugin 126, harness 117, rls 212, the two tests 105 and 24), and 15 changed values, each of them prose: 2 description: values, 1 reason:, 1 detail:, 2 report lines, 4 new Error messages, 2 logger warn messages, 1 remedy string, and the 2 test assertions. The later merge of origin/main touches none of these files.

The dev plugin's no-auth warning first gained a fifth literal; commit 03a490204b re-wrapped the sentence across the original four so the skeleton stays equal.

The ledger

node scripts/check-doc-authoring.mjs --census-ledger, written to a scratch file first so its no-growth check reads the committed baseline, then installed:

occurrences (file, id) pairs files
the four rows before 14 7 4
the four rows after 0 0 0
whole ledger before (7c5a311a58, also d6d6e872e5) 399 279 107
whole ledger after 385 272 103

15 lines deleted, 0 added; every other row is byte-identical. After merging origin/main (d6d6e872e5) the recomputed ledger is byte-identical to the committed one. pnpm check:doc-authoring: before "338 pinned site(s) across 107 file(s) ... no growth, no burn-down unrecorded", after "325 pinned site(s) across 103 file(s) ... no growth, no burn-down unrecorded".

Pins, and that they can fail

Two tests asserted an id. Each now asserts the words that carry the decision, and each was ablated through scripts/ablation-replace.mjs on the committed fix (anchor must hit once, blob must change, restore proven by blob equal to HEAD and an empty git diff HEAD). Both tests import the source by relative path, so no build sits between the mutation and the run.

Pin Asserts now Ablation Result
plugin-dev/src/dev-plugin-optional-load-failure.test.ts:283 "always denied, with no setting that turns that off" "turns that off" to "turns that on" in dev-plugin.ts 1 failed / 9 passed
plugin-hono-server/src/hono-transport-only.test.ts:92 "transport adapter and serves neither" "serves neither" to "serves nothing" in hono-plugin.ts 1 failed / 4 passed

Restored, the two files run 10 passed and 5 passed.

No other pin asserts any of the old strings: the whole repository was searched (packages/** including qa and dogfood, examples, docs/qa/**, snapshots and JSON). The dogfood RLS suites print formatRlsReport only as an assertion message, and docs/qa/platform-checklist/RUNNER.md quotes the summary lines and N of M declared position(s) probed, which are unchanged.

What ships

Measured on the built dist/ of each package: every rewritten sentence is present (in both the ESM and CJS bundles) and no old spelling is. All three packages publish dist, so the changeset carries patch for @objectstack/verify, @objectstack/plugin-dev and @objectstack/plugin-hono-server.

Verification (head 36281b515a, after merging origin/main d6d6e872e5)

Heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-20752-s4); each verdict line reads VERDICT command-exit 0 unless stated.

  • Build: pnpm turbo run build over the three packages and their dependency closures, 38/38 tasks.
  • Tests: @objectstack/plugin-hono-server 27 files / 324 tests, @objectstack/plugin-dev 9 / 86, @objectstack/verify 16 / 120, all passed (before and after the merge). The dogfood RLS runner oracle (packages/qa/dogfood/test/rls-runner.test.ts, which drives the rls-hole path against the rebuilt @objectstack/verify dist): 17 passed.
  • Typecheck: typecheck of all three packages, each tsc --noEmit plus check:test-typecheck (the test layer, the two pins included) OK.
  • Gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 71 commands; all 71 run with exit codes recorded; --ran reconciliation: "71 derived famil(ies) accounted for — 71 run, 0 NOT-MEASURED". check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (exit 3, no dist for unrelated packages); after a full workspace build (72/72 tasks, 71 cached) it passed and that rerun is the recorded result.
  • Lint, a proven narrowing: eslint's own config places 6 of the 8 changed paths in its population (isPathIgnored false and a matching config object; the changeset and the JSON ledger are outside it). --no-inline-config with the json formatter: 6 file results, 0 errors, 0 warnings. Invariance: this repo's config enables no type-aware linting (eslint.config.mjs, the note at lines 326-328) and its only disk reads are two baselines this diff does not touch, so no untouched file's verdict can move. The full pnpm lint is CI's.

Acceptance notes

  • The seat's staging comment (5930275362) listed a dead tracker number in the plugin-dev test title dev-plugin-security-enforcement-warning.test.ts:121 for this stage. Claim 5938321786 names four files and not that one, and a test title is not on the ledger, so it is not touched here. Carrier: the seat, when it stages what remains.
  • Code comments in the same four files still cite these cards. They are not on the ledger, and the claim keeps them out of scope.
  • packages/qa/dogfood/test/enterprise-organizations.ts:184 carries the 4719 twin of the harness remedy. It sits in the qa row, which stage 5 owns.

Generated by Claude Code

claude added 4 commits October 1, 2026 18:59
…ch decision in words instead of a tracker number (stage 4)

The verify RLS runner's report, persona provisioning errors and probe
metadata, the verify harness's organizations remedy, the dev plugin's
tenancy refusals and no-auth warning, and the Hono server's no-API warning
pointed at tracker numbers. The numbers go; where a sentence leaned on one,
it now says the decision in words. Two pins that asserted a number now
assert the decision sentence. The prose-id ledger is recomputed with
--census-ledger: the four rows leave, nothing else moves.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
Re-wraps the rewritten sentence across the warning's existing literals
instead of adding a fifth, so the change stays text-only at the AST level.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/s label Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/plugin-dev, @objectstack/plugin-hono-server, @objectstack/verify, touching 7 documentable anchor(s).

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

  • content/docs/kernel/services-checklist.mdx (via DevPlugin (symbol, a top-level class))

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

  • content/docs/releases/v17/17-0.mdx (via bootStack (symbol, a top-level function))

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
  • 1 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 — 11 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 62b90d74f757dbdd413780819bae54915af5a149 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from adde1dd1cfb4d4dd06a639961047bca5c1fc1e76 — the merge of head 36281b515a9f3c2ada618809dd3578694407e15a into base 62b90d74f757dbdd413780819bae54915af5a149, 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 adde1dd1cfb4d4dd06a639961047bca5c1fc1e76 && git checkout adde1dd1cfb4d4dd06a639961047bca5c1fc1e76
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 62b90d74f757dbdd413780819bae54915af5a149 36281b515a9f3c2ada618809dd3578694407e15a && git checkout -B drift-repro 62b90d74f757dbdd413780819bae54915af5a149 && git merge --no-ff 36281b515a9f3c2ada618809dd3578694407e15a

node scripts/docs-audit/affected-docs.mjs --json 62b90d74f757dbdd413780819bae54915af5a149

⚠️ 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 62b90d74f757dbdd413780819bae54915af5a149 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 1, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 20:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 20:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 49d2a24 Oct 1, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20752-stage4-verify-plugin-strings branch October 1, 2026 20:49
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…rmance ledgers and harness refusals state each decision in words instead of a tracker number (stage 5a) (objectstack-ai#21250)

Part of objectstack-ai#20752
Clause-②: no

**Stage 5a of the `domain:cli` lane under the maintainer's A / A ruling
(5902360492): every ledgered tracker number in the `packages/qa` strings
outside the authz conformance matrix, plus the `plugin-dev` test title
folded in from the dead-citation sweep.** The matrix file (76
occurrences) is stage 5b, which carries the closing keyword, so this PR
has none. Text only: no expected status, error `code`, verdict, route,
field, export or control flow moves.

## What this does

The dogfood harness refusals (`assertArmed`, the build stand-in, the
showcase security helper, the multi-org remedy), the expression and
search conformance ledgers' summary and enforcement cells, nine fixture
manifests and one permission-set label, and the downstream-contract
manifest sent the reader to a tracker number for the reason behind them.
In form D, as stages 1 to 4 applied it (PR objectstack-ai#21172, PR objectstack-ai#21188, PR objectstack-ai#21219,
PR objectstack-ai#21231), the number goes. Where the sentence already said what was
decided, only the citation goes. Where it leaned on the number, it now
says the decision in words.

All 26 ledgered occurrences in the claim's 16 files (claim
`5940325318`), at 25 string sites, plus the one `plugin-dev` `describe`
title the claim adds (not on the ledger). Every cited card was read
first. Three answer 404 or cannot be read here and were read through
their landing commits instead (below).

### Rewritten in words

| Where (head line) | Cited | The text now says | Decision read from |
|---|---|---|---|
| `dogfood/test/armed.ts:136-138` empty-declaration refusal | 8074 |
"...the exact defect class this helper exists to close, a fixture that
passes while the control it measures is not engaged." | card 8074 (an
org-less fixture cannot observe the gated write floor, so a real 403
records as a passing cell; direction 2: a helper that refuses) |
| `dogfood/test/armed.ts:162` DISARMED refusal | 8074 | the bracketed
tracker tag becomes `assertArmed():`, the same lead the
empty-declaration refusal already uses; the rest of the sentence already
said the assertions would pass without testing anything | card 8074 |
| `expression-conformance.ledger.ts:144` `sharing-condition` summary |
1887 | "(ADR-0058 D3: compiled from the authored CEL, a faithful
lowering rather than a divergent hand-written filter)" | card 1887 (the
spec condition was never compiled; enforce or remove) and ADR-0058 D3
(the CEL condition compiles to `criteria_json`, a faithful lowering of
the authored CEL) |
| `expression-conformance.ledger.ts:293` `settings-visibility`
enforcement | 7327 | "The spec DECLARES that same grammar
(`SettingsVisibilityInputSchema`) rather than CEL, so it is refused at
publish/parse too" | card 7327 (narrow the declaration to the grammar
the save-time evaluator implements, measured 1 against 93) |
| `expression-conformance.ledger.ts:303`
`cel-action-param-option-visible` summary | 5016 |
"(params[].options[].visibleWhen, the same per-option key a field's
option list declares)" | card 5016, maintainer ruling B (reuse the field
option vocabulary), landed as commit `f6609e6ae2`; `ui/action.zod.ts`
records the per-key outcome (`visibleWhen` opened) |
| `fixtures/attachments-fixture.ts:140` manifest | 2755 | "...exercising
the non-admin attachment permission matrix: ..." | card 2755 item 2
(dogfood the non-admin attachment permission matrix) |
| `fixtures/comments-fixture.ts:139` manifest | 4630 | "...exercising
the record-level comment permission matrix: ..." | card 4630
(`sys_comment` gains record-level authorization, mirroring attachments)
|
| `fixtures/label-scope-fixture.ts:55` manifest | 3602 | "Deal → vendor
lookup exercising the dimension-label read scope: a vendor the reader
cannot read is shown by raw id, never by name." | card 3602's first
residual (the per-record label read behind a grouped lookup carried no
read scope); the fixture's own header |
| `fixtures/rls-owner-fixture.ts:57` manifest | 1994 | "...exercising
the cross-owner by-id-write invariant: a caller that cannot read a
record must not be able to write it." | PR 1994 (a by-id write must pass
the row-level write filter); stage 4's wording of the same invariant in
`verify --rls` |
| `fixtures/rls-owner-fixture.ts:105` permission-set label | 1994 | `RLS
Fixture Member — owner-scoped reads only (no write policy: the
by-id-write hole shape)` | PR 1994; the fixture header (owner policy on
SELECT only, the hole class's authoring shape) |
| `dogfood/test/showcase-security.ts:66` refusal | 5491 | "...the CLI
wiring these fixtures model cannot be reproduced, and the platform
baseline alone grants a member no object access" | card 5491, maintainer
ruling of 2026-08-07 (the wildcard grant leaves `member_default`; the
baseline is explicit-allow) |
| `downstream-contract/src/stack.ts:19` manifest | 2035 | "Frozen
third-party consumer gating spec backward compatibility: a spec change
that needs this fixture edited to stay green is breaking." | landing
commit `92647c13aa` (the downstream-consumer contract, frozen: a spec
change that requires editing it is breaking), the work done under card
2035; the package README states the same contract |

### Citation only (the sentence already stated the decision)

- `dogfood/test/build-shaped-artifact.ts:192`, `:225`, `:265` (6293,
answers 404): the three refusals already say what a stand-in must do
instead of `JSON.stringify` (fix the walk, never the assertion; a
headless husk is what a plain stringify leaves; a function left in the
artifact would be dropped without a sound). Read from landing commit
`c39a911ae6` (fixtures get the real lowering, and the stand-in throws
naming what went missing).
- `dogfood/test/enterprise-organizations.ts:184` multi-org remedy
(4719): the twin of the `verify` harness remedy that stage 4 rewrote
citation-only. It already says the app's own declaration is what counts
and a transitive reach is not enough. Card 4719 (option 2: the host
declaration decides).
- `expression-conformance.ledger.ts:293` (7310): "Fail-closed since"
plus the card becomes "Fail-closed:"; the sentence goes on to say a
predicate outside the grammar refuses the save. PR 7310.
- `expression-conformance.ledger.ts:319` (objectui card 3067):
"selection-bar bulk action per-record eligibility
(bulkActionDefs[].visible)". This session has no read access to
`objectstack-ai/objectui` (403) and the dispatch forbids attaching it,
so the decision was read from this repository's record:
`ui/bulk-action.zod.ts`'s `visible` describe (evaluated once per
selected record; the button is offered when at least one passes) and the
row's own enforcement cell, both unchanged.
- `expression-conformance.ledger.ts:343`, `:350` (objectui card 2614):
the two summaries already say "per-record visibility" and "per-record
disabling" of the built-in row Edit/Delete. Read from this repository's
landing commit `627f225f2c` (`userActions.edit/delete` accept per-record
CEL predicates).
- `fixtures/email-template-materialization-fixture.ts:59` (4509) and
`fixtures/webhook-materialization-fixture.ts:55` (3461): each already
says the stack entries materialize into the rows the runtime reads;
`ADR-0054` stays.
- `fixtures/flow-durable-suspend-fixture.ts:101` (4470): already says
the flow suspends, persists and resumes after a cold boot.
- `fixtures/flow-function-effect-fixture.ts:69` (4396): already says the
declared effect reaches the run summary.
- `fixtures/flow-runas-fixture.ts:125` (1888): already says `flow.runAs`
identity is enforced.
- `dogfood/test/search-conformance.ledger.ts:49` (4254): already says a
name outside the set is a 400 at the REST ingress, not silently dropped.
- `plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts:121`
`describe` title (10036, answers 404; the fold site from the
dead-citation sweep, ACCEPT `5939681859`): the bracketed tag goes; the
title already says the warning must fire when `SecurityPlugin.start()`
bailed. Read from landing commit `7552e03375` (the warning probes the
published `security` service in `start()`).

## Text only, proven on the AST

A scratch script (not committed) parses each changed TypeScript file at
BASE `a7d9768ecd` and at `75e33f4c45` (the stage commit; the later merge
of `origin/main` touches none of these files), blanks every string value
and template span, and compares the remaining node sequence (kinds,
identifiers, numerals). Result: identical skeleton in all 17 files,
equal node and string-value counts per file, and 28 changed string
values, all prose: the 26 sites above plus two neighbouring literals of
the `armed.ts:136-138` refusal, re-wrapped so the message keeps its four
literals.

## The ledger

`node scripts/check-doc-authoring.mjs --census-ledger`, written to a
scratch file first so its no-growth check reads the committed baseline,
then installed:

| | occurrences | (file, id) pairs | files |
|---|--:|--:|--:|
| the 16 stage rows before | 26 | 21 | 16 |
| the 16 stage rows after | 0 | 0 | 0 |
| `authz-conformance.matrix.ts` (stage 5b) | 76 | 46 | 1 |
| whole ledger before (`a7d9768ecd`) | 385 | 272 | 103 |
| whole ledger after | 359 | 251 | 87 |

53 lines deleted, 0 added; every other row is byte-identical, the matrix
row included. After merging `origin/main` (`ef96c9ede7`) the recomputed
ledger is byte-identical to the committed one. `pnpm
check:doc-authoring`: before "325 pinned site(s) across 103 file(s) ...
no growth, no burn-down unrecorded", after "300 pinned site(s) across 87
file(s) ... no growth, no burn-down unrecorded".

## Pins

No test asserts any of the old strings: every rewritten fragment and
every cited number inside an assertion was searched across the
repository, with no hit outside the sites themselves. The two existing
pins that read rewritten messages, `armed.dogfood.test.ts` matching
`this fixture is DISARMED` and `arming declaration is EMPTY`, still
match, and pass. With nothing re-pointed there was no pin to ablate. The
`merge-queue-triage` job-log fixtures under `scripts/fixtures/` quote
the old `describe` title as recorded CI output; they are history and
stay as they are.

The run itself shows one rewritten string live: the multi-org skip line
in the dogfood run now prints "...being reachable as somebody else's
transitive dependency is not enough. Set
OS_TEST_MULTI_ORG_ENABLED=1...".

## What ships

Nothing. `@objectstack/dogfood` and `@objectstack/downstream-contract`
are `private: true`. `@objectstack/plugin-dev` publishes `dist`, and its
built `dist/` holds the new `describe` title 0 times; the control, the
warning's own `NOT enforced` text, is found in `dist/index.js`. So no
changeset, and the PR takes `skip-changeset`.

The downstream-contract fixture is frozen against spec-driven edits (its
README). This edit is prose in the manifest description, made for this
ruling and not to make a spec change pass.

## Verification (head `3efe6499b8`, after merging `origin/main`
`ef96c9ede7`)

Heavy runs went through `scripts/pm/os-verify-lock.sh` (slot
`issue-20752-s5a`); each verdict line reads `VERDICT command-exit 0`.

- Build: `pnpm turbo run build` over the dependency closures of
`@objectstack/dogfood`, `@objectstack/downstream-contract` and
`@objectstack/plugin-dev`, 63/63 tasks, before and after the merge.
- Tests, before and after the merge: `@objectstack/plugin-dev` 9 files /
86 tests passed (the renamed `describe` ran under the verbose reporter
with its 4 tests green); `@objectstack/downstream-contract` 3 / 31
passed; `@objectstack/dogfood`, every test file that imports a changed
module directly (46 files, run in two batches): 45 passed, 1 skipped
(`rls-multitenant`, which needs the enterprise organizations package
this repository does not ship), 388 tests passed, 3 skipped. The full
dogfood suite is CI's `Dogfood Regression Gate`.
- Typecheck: `@objectstack/dogfood` (`tsc --noEmit`; `--listFiles` shows
all 15 changed dogfood files in its program),
`@objectstack/downstream-contract`, and `@objectstack/plugin-dev` (`tsc
--noEmit` plus `check:test-typecheck`, which compiles the test layer:
OK).
- Gates: `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 67 commands; all 67 run
with exit codes recorded; `--ran` reconciliation: "67 derived famil(ies)
accounted for — 67 run, 0 NOT-MEASURED". `check:dual-build-cjs-loads`
first answered PREREQUISITE NOT MET (exit 3, no dist for unrelated
packages); after a full workspace build (72/72 tasks, 71 cached) it
passed, and that rerun is the recorded result.
- Lint: the full `pnpm lint` (`eslint . --no-inline-config`) at
`3efe6499b8`: exit 0, no findings.

## Acceptance notes

- `expression-conformance.ledger.ts:322` is a comment that says the bulk
row "reached the ledger in" one card "not" another, both spelled bare.
The second is objectui's card, and a bare number reads as this
repository's. It is a comment, not on the ledger, so it is untouched
here. Carrier: the dead-citation sweep.
- `packages/qa/downstream-contract/package.json`'s `description` and its
README still cite card 2035, and dogfood test titles and `it` names
still carry tracker numbers. None of these is on the ledger or the
claim. Carrier: the seat, when it stages what remains after 5b.
- Code comments in the 17 files still cite these cards. They are not on
the ledger, and the claim keeps them out of scope.

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

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…n words instead of a tracker number (stage 5b) (objectstack-ai#21265)

Fixes objectstack-ai#20752
Clause-②: no

**Stage 5b, the last stage of the `domain:cli` lane under the
maintainer's A / A ruling (5902360492): the authz conformance matrix.**
Stages 1 to 5a (PR objectstack-ai#21172, PR objectstack-ai#21188, PR objectstack-ai#21219, PR objectstack-ai#21231, PR objectstack-ai#21250)
emptied every other row of this lane; this PR empties the last one, so
the lane's share of the ledger burn-down is zero once it lands. Text
only: no row id, `state`, `covers` key, `proof` file or enforcement site
moves, and the file's code comments are untouched.

## What this does

The `summary`, `enforcement` and `note` strings of
`packages/qa/dogfood/test/authz-conformance.matrix.ts` sent the reader
to a tracker number for the reason behind a row. In form D, as the
earlier stages applied it, the number goes. Where the sentence already
said what was decided, only the citation goes. Where it leaned on the
number, it now says the decision in words.

All 76 ledgered occurrences of 46 cards, at 43 string sites (claim
`5942403129`, ledger read at `a23be7498e`): 16 `summary`, 10
`enforcement`, 17 `note` strings. 22 sites now say something in words;
21 already stated the decision and only lose the citation.

Every cited card was read first. Six answer 404 and were read through
the commit that decided them: 10243 (`266436a7f`), 12176 (`7986d973f`),
11757 (`4d25d22d4`), 8710 (`04d03c3a0`), 8711 (`2ce1eb41b`) and 8811
(`d6e793507`). The 10145 and 10243 phrases on the automation row reuse
stage 3's `route-ledger.ts` wording for the same decisions, and the 5519
floor is named the way stage 3 named it (the domain-wide anonymous
floor, refused before dispatch).

Two notes cite `describe` titles in other packages that carry a
bracketed tag. Those titles are quoted without the tag, so each quote
stays a literal substring of the real title and is still findable by
search.

### The 43 sites (line numbers are the same at base and head)

| Site | Cited | Form | The text now says (new fragment) | Decision read
from |
|---|---|---|---|---|
| `:161` `rls-by-id-write` summary | 1994 | citation only | by-id write
enforcement | PR 1994 (RLS re-checked on the pre-image of a by-id
update/delete) |
| `:162` `rls-by-id-write` enforcement | 7665 | citation only |
write-scope DERIVATION: when no update/delete-class policy applies |
card 7665 via PR 7792 (an empty write-class policy set derives its scope
from the caller's select narrowing); the sentence after the colon
already says it |
| `:164` `rls-by-id-write` note | 7685, 7665, 7665, 7792, 7685 | words
(tag dropped; three phrases worded) | Re-verified ... — that was the
hole through which a contributor PATCHed records it could not read, and
... since the fix that derives a missing write scope from the select
narrowing, that file carries ... whose probe persona holds object
read+edit narrowed by select-only RLS and so reaches this class — | card
7665 (a by-id write was not gated by record visibility under select-only
RLS); PR 7792 (option A: derive the write scope from the select
narrowing); card 7685 comment 5264791326 (measurement first: a probe
persona with object read+edit narrowed by select-only RLS; both rows
stay enforced) |
| `:174` `controlled-by-parent` note | 7685, 7665 | tag dropped; one
phrase worded | Re-verified as `enforced` on its OWN evidence ... when
the select-derived write-scope derivation its master depends on is
ablated. | card 7685 comment 5264791326 (re-verified by measurement, not
downgraded); card 7665 / PR 7792 |
| `:177` `multi-tenant-write-postimage` summary | 2937 | words | (forged
INSERT / Finding 1 re-point — a forged OR re-pointed organization_id
cannot cross the tenant wall) | card 2937 (an INSERT carrying a forged
organization_id crossed the tenant wall; Layer 0 gains an insert
post-image check) |
| `:179` `multi-tenant-write-postimage` note | 2937, 2937, 2937 |
citation only + suite titles named in words | INSERT a forged
cross-tenant organization_id or UPDATE ... (the "Layer 0 insert
post-image tenant guard" suite + the Finding 1 "Layer 0 update
post-image tenant guard (cross-tenant re-point)" suite) | card 2937; the
two describe titles in plugin-security/authz-matrix-gate.test.ts, quoted
without their bracket tags so each stays a substring of the real title |
| `:181` `multi-tenant-exemption-posture` enforcement | 2956 | citation
only | reads the carried ctx.posture rung (ADR-0099 D1) | PR 2956 (carry
the derived posture rung on ExecutionContext); the phrase 'the carried
ctx.posture rung' already says it |
| `:182` `multi-tenant-exemption-posture` note | 2937 | suite title
named in words | (the Finding 2 "Layer 0 cross-tenant exemption requires
the platform posture" suite + "ADR-0099 P1 ...") | the describe title in
plugin-security/authz-matrix-gate.test.ts |
| `:195` `org-write-validation` note | 2937 | words | — the
forged-organization_id INSERT defect one call site down. | card 2937 |
| `:213` `anonymous-deny-meta` summary | 2567 | words | (uniform
anonymous posture, surface 1) | card 2567 (the anonymous-deny posture
must be uniform across every HTTP surface that reaches ObjectQL) |
| `:228` `anonymous-deny-meta` note | 11373, 12176 | citation only +
words | For most of this row's life ... five since the retirement of
slash-bearing metadata item names un-mounted the compound save | card
11373 (measure first; the note goes on to state the measured refusal);
card 12176 answers 404, read through landing commit 7986d97 (stage 3
of the ruled retirement of slash-bearing metadata item names: un-mounts
the compound arities) |
| `:236` `anonymous-deny-actions` summary | 2567, 5519 | words |
(uniform anonymous posture, surface 2: refused 401 before dispatch, as
`/data` and `/meta` are) | card 2567; card 5519 (anonymous /actions and
/automation requests are refused 401 before dispatch, the same baseline
as /data and /meta) |
| `:244` `anonymous-deny-actions` note | 5519 | words | — before the
gate, an anonymous `POST /actions/showcase_task/showcase_mark_done/:id`
was measured answering 200 with the update applied. | card 5519 |
| `:245` `anonymous-deny-automation` summary | 2567, 5519 | words |
(uniform anonymous posture, surface 3: refused 401 before dispatch, as
`/data` and `/meta` are) | card 2567; card 5519 |
| `:246` `anonymous-deny-automation` enforcement | 10145, 10243, 7900,
3801, 5561 | words (stage 3 route-ledger twin wording) | DELETE /:name`,
the definition writes on the metadata plane, plus enablement `POST
/:name/toggle` since the 2026-08-23 ruling that enablement is an
authoring write, ... the run-state reads (the `sys_automation_run` read
grant) and `resume` (keyed on the node the run is suspended on,
fail-closed for a node that declares no resumeAuthority) carry their own
| card 10145 (flow definition writes are authored metadata, so
manage_metadata gates them); card 10243 answers 404, read through
landing commit 266436a (enablement is an authoring write, ruling of
2026-08-23); card 7900 via commit 627e65a (run-state reads consult
the sys_automation_run read grant); card 3801 (resume gated on the
suspended node); card 5561 (no declared resumeAuthority fails closed);
the 10145 and 10243 phrases reuse stage 3's route-ledger.ts wording |
| `:253` `anonymous-deny-automation` note | 5519 | words | which the
original anonymous-surface report did not record. | card 5519 |
| `:263` `anonymous-deny-packages` summary | 7033, 7023 | citation only
| anonymous-deny on the package-management surface | cards 7033 and 7023
(the /packages domain carried no authorization predicate; the row's
enforcement already states the domain-wide gate); same shape as the
sibling analytics row |
| `:300` `realtime-delivery-authz` summary | 2992 | words | (a latent
surface: identity admission is owed before any client transport ships) |
card 2992 (GraphQL and realtime must satisfy identity admission before a
client transport ships) |
| `:302` `realtime-delivery-authz` note | 9083, 9083 | tag dropped +
words | Clearing that red ... Before that admission rule landed, this
note promised a gate that did not exist | card 9083 (a TRANSPORT-WIRED
key may be classified only by an enforced row); the sentence after the
tag already says it |
| `:310` `mcp-http-identity` enforcement | 2698 | citation only | (403
on none) | card 2698 (OAuth 2.1 for /api/v1/mcp, scope-gated tool
families); the sentence already says it |
| `:320` `mcp-http-identity` note | 3167 | citation only | proven
end-to-end: the proof boots | card 3167 (identity admission first); the
sentence after the colon states what the proof drives |
| `:332` `readonly-static-write` summary | 2948, 3003, 3043 | citation
only + words | UPDATE AND INSERT (first at the data-write ingress;
in-engine ...) | cards 2948 and 3003 (strip static readonly on
non-system UPDATE); card 3043 (tighten the INSERT exemption, first
enforced at the data-write ingress) |
| `:333` `readonly-static-write` enforcement | 2948, 5591 | citation
only | (caller-supplied VALUES only — ... the caller also sent) | card
2948; card 5591 (strip the caller-supplied value, never a hook stamp);
the parenthetical already says both |
| `:335` `readonly-static-write` note | 3003, 3043, 3003 | words | The
originating field report: ... The INSERT face followed: ... a step
SHORTER than the draft-then-PATCH route, | card 3003 (readonly was
UI-only; a non-admin self-approved by PATCH); card 3043 (the INSERT
exemption let the same caller POST an approved record) |
| `:340` `declarative-rbac-seeding` summary | 2077 | citation only |
seeded at boot | card 2077 (activate declarative roles + sharingRules at
runtime) |
| `:355` `ownership-anchor-guard` summary | 3004 | citation only |
without the transfer grant | card 3004 (owner_id is system-managed for
non-privileged writers) |
| `:358` `bulk-write-owner-scoping` summary | 2982 | citation only | not
just single-id writes | card 2982 (bulk writes owner-scoped on
OWD-private objects) |
| `:361` `public-form-managed-anchors` summary | 3022 | citation only |
(owner_id / organization_id / audit / id) | card 3022 (a public-form
submit cannot supply owner_id or other server-managed anchors) |
| `:362` `public-form-managed-anchors` enforcement | 3004 | words |
complements the step 3.5 owner-anchor guard | card 3004 |
| `:378` `hierarchy-widening` enforcement | 7807 | citation only | the
runtime was narrowed to the declaration | card 7807 (business_unit
expands exactly one unit, as declared) |
| `:381` `rls-compiler-fail-closed` enforcement | 4983 | citation only |
hoisted out of plugin-security so lint/... | card 4983 (wire the
ADR-0056 D4 authoring gate to the one predicate definition) |
| `:385` `secure-by-default-posture` enforcement | 11757 | words | (the
gate's other carrier, sys_scim_provider, retired once the stable SCIM
line stopped deriving a provider model) | card 11757 answers 404, read
through landing commit 4d25d22 (retire the rc.1-era sys_scim_provider;
stable @better-auth/scim derives no scimProvider model) |
| `:387` `flow-run-as` summary | 1888 | citation only | under the run's
effective identity | card 1888 (enforce runAs) |
| `:390` `flow-run-as` note | 1888 | words | but its enforce-or-remove
decision chose ENFORCE and implemented it for flow data nodes | card
1888 (decision required: enforce or remove; enforced) |
| `:420` `permission-set-active` summary | 8613 | citation only |
(ADR-0049) | card 8613 (the active flag on both grant catalogues is
enforced) |
| `:422` `permission-set-active` note | 8613 | citation only | "the
`active` flag on the grant catalogues (ADR-0049)" | card 8613; the
describe title in core/security/resolve-authz-context.test.ts, quoted
without its tag so it stays a substring of the real title |
| `:423` `position-active` summary | 8613 | citation only | (ADR-0049) |
card 8613 |
| `:443` `grant-validity-window` enforcement | 10982 | citation only |
accessible_org_ids and the org-administration role projection | card
10982 (window-filter the role projection too); the present-tense list
already says it |
| `:445` `grant-validity-window` note | 8811, 8711, 8710, 10982, 11089,
10982, 9377, 7976 | tags dropped + words | NO `covers` ... Per the
2026-08-15 maintainer ruling ... by the 2026-08-15 ruling that
access-conferring paths filter and addressing paths do not, because
approval ROUTING ... `sys_member` ... last-admin-guard.ts's ... the role
projection as window-filtered now; ... is now cited: ... the
mutual-attribution contract (a cited proof file names the rows it
proves) is satisfied. | 8811 (404) via d6e7935 (adds this row); 8711
(404) via 2ce1eb4 (ruled narrowing of the completeness claim to
routes); 8710 (404) via 04d03c3 (ruling 2026-08-15: access-conferring
paths filter deactivated positions, addressing paths do not); card
10982; card 11089 (the last-admin-guard note went stale after 10982);
card 9377 (cite delegation-of-duty as this row's proof); card 7976 (a
proof file names the rows it proves, checked both ways) |
| `:452` `agent-visibility` summary | 1901 | citation only | listing
scope | card 1901 (agent visibility not enforceable without owner/org
anchors; removed per D8) |
| `:453` `agent-visibility` note | 1901, 1884 | citation only |
`visibility` deleted) ... at the chat route; | card 1901; card 1884
(enforce access/permissions at the chat route) |
| `:462` `requireAuth-removed` note | 3963, 7976 | words + tag dropped |
ADR-0056 D2, completed by deleting the switch once every session-less
surface was declared: the `requireAuth: false` opt-out is RETIRED ...
The `showcase-anonymous-deny.dogfood.test.ts` CITATION WAS DROPPED |
card 3963 (step 1: public as a declared capability; step 2: delete
api.requireAuth, an auth-less stack fails at boot); card 7976 (mutual
attribution, already named in the sentence) |
| `:466` `allow-transfer-restore-purge` note | 1883, 3004, 12497, 1883 |
words + citation only | ADR-0049 → roadmap M2, which builds the
lifecycle ops and their RBAC bits as one batch: the ops still do not
exist ... owner_id door. ... RETIRED 2026-08-26 (maintainer ruling:
retire the two bits now rather than carry them unenforceable until M2):
| card 1883 (M2: build undelete/purge and their RBAC bits in one batch);
card 3004; card 12497 (ruled 2026-08-26: retire allowRestore/allowPurge,
the keys return with M2) |

## The ledger

`scripts/doc-authoring-prose-id.baseline.json`, recomputed with `node
scripts/check-doc-authoring.mjs --census-ledger` (refuses any growth):

| | occurrences | (file, id) pairs | files | matrix row |
|---|--:|--:|--:|--:|
| base `434c6c7cab` | 359 | 251 | 87 | 76 occurrences / 46 cards |
| head | 283 | 205 | 86 | absent |

- Exactly the matrix row leaves: 48 lines deleted, 0 added. The other 86
rows compare equal as JSON.
- Recomputed again after merging `origin/main` (`8dea55d314`):
byte-identical.
- `pnpm check:doc-authoring`: exit 0 before and after. Its cross-package
line reads 300 pinned sites across 87 files at base and 257 across 86 at
head, "no growth, no burn-down unrecorded".
- The census's other 257 sites are the same before and after (file, line
and ids).

## Text only

A scratch TypeScript-AST comparison of the file at base `434c6c7cab` and
head: 1695 skeleton nodes on both sides, identical; 280 string literals
on both sides, 43 values changed, owned by `summary` 16, `enforcement`
10, `note` 17 and nothing else; 255 comment trivia blocks on both sides,
byte-identical. Its three controls each behave: a renamed property key
reads "skeleton DIFFERS", a changed `state` value is reported as owned
by `state`, and an edited comment reads "not identical".

## Pins

None. Nothing mechanical reads these three fields: the companion test
imports the rows for `id`, `state`, `proof`, `covers` and `enforcement`
presence, and the census and blind-spot test read only the header
docblock and the `covers` arrays. A whole-repo fixed-string search for
the text around each of the 76 removed ids (224 fragments, `docs/qa/**`
included) found 12 fragments with hits, every one a code comment, a
release-owned CHANGELOG entry, a `describe` title or a liveness note,
none asserting this file's text. So no pin moved and no ablation is
owed.

## Verification

- Build: `pnpm turbo run build --filter='@objectstack/dogfood^...'
--concurrency=2`: 63/63 tasks.
- Tests: `pnpm --filter @objectstack/dogfood exec vitest run
--maxWorkers=2 test/authz-conformance.test.ts
test/authz-probe-blind-spot.test.ts`: 2 files, 90 tests passed. These
are the only consumers of the module and of its text.
- Typecheck: `pnpm --filter @objectstack/dogfood typecheck` exit 0;
`--listFilesOnly` lists the matrix file and the companion test.
- Gates: `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `3005a8b6d1` derives 54
commands; all 54 run, exit 0. `check:dual-build-cjs-loads` first
answered exit 3 (PREREQUISITE NOT MET: 8 unrelated packages without
`dist/`); after a full workspace build (72/72) it exits 0 and that rerun
is the recorded result. `--ran` reconciliation: 54 derived, 54 run, 0
NOT-MEASURED, 0 UNRUN.
- Lint: full `pnpm lint` at `3005a8b6d1`: exit 0, no findings.
- Tests and typecheck ran at `0dbdbd759b`; the merge after it brought
one `docs/adr` file and no package input.

## Changeset

None, with `skip-changeset`: `@objectstack/dogfood` is `private: true`,
and the ledger is a repository script file. No `.changeset/*.md` is
touched.

## Acceptance notes

- **Code comments** in the matrix file still cite cards (33 comment
lines, the bracketed tracker tags and the block-comment headers among
them). They are not runtime strings and not on the ledger, so they are
outside ruling 5902360492 and this claim. Noted, not filed.
- **Two `describe` titles the notes quote** carry a bracketed tag:
`plugin-security/src/authz-matrix-gate.test.ts` (the Layer 0 insert,
update and exemption suites) and
`core/src/security/resolve-authz-context.test.ts` (the `active`-flag
suite). Test bodies are outside the gate's reading and belong to those
packages, not to this lane. Noted, not filed.

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

---------

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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants