Skip to content

fix(plugin-audit): a read of the compliance ledger returns only the rows about records the caller can read (#21175) - #21194

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-21175-ledger-parent-read-gate
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-21175-ledger-parent-read-gate

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #21175
Clause-②: no

A read of the compliance ledger (sys_audit_log) now returns only the rows about records the caller can read. This implements triage's ruling A on the card (5932888473): the ledger takes the activity stream's parent-record read gate (#20833, PR #21069), and that gate reads the engine's own answer.

Status. Draft. The product consequence for admins (see Acceptance notes) is awaiting the maintainer's decision on #21175; this PR's behaviour is unchanged by patch round 1.

Patch round 1: what changed since the first push

Measured first, on a real boot (classes only)

The re-measure was private and its readings stay in the dispatch's scratch. It ran on main at b9087d77 (PR #21171 in), and again at this branch's first head. The stack was bootStack, org-bound, with the real SecurityPlugin, auth, REST and AuditPlugin. The rows were written by the CRUD mirror and the auth-event sink. The member holds the ledger read through one explicit permission set; the admin is the seeded admin. "Parent" means the same caller's read of the row's record through the data door.

door caller parent, through the data door before after
list GET /data/sys_audit_log member 404: a private record's create, update and delete rows returned absent
list member 404: other users' sessions (their login rows) returned absent
list member 200 returned returned
by id GET /data/sys_audit_log/:id member 404 200 404
list, by id admin 200: every existing record returned returned
list admin, member the record no longer exists (delete rows, a deleted record's other rows, logout rows) returned absent
list total member (any) 51 42, the rows returned
list total admin (any) 51 47, the rows returned

What changed

Row classes: the stated answer for rows the gate cannot judge

Measured per writer:

  • About a record (object_name + record_id): the CRUD mirror's create, update and delete rows; record-view read rows; plugin-auth's administrative create and update on a user; and login / logout, which name the session. Judged by the record gate.
  • About a record that no longer exists: no caller can read the record, so these rows are excluded for every caller that is not system context, admins included. That covers every delete row, every logout row (sign-out deletes the session, measured), and a sign-in row whose session has since been removed. The rows stay stored.
  • About no record (no record_id, and an action that is not a record action): the run-level import, config_change and platform_admin_standing_change rows, and an auth event without a session id. These are outside the gate's class and are served under the ledger's own grant, as before. Why this differs from the activity gate, which excludes every row that names no parent:
    • the activity stream has no platform producer of such rows;
    • these rows have three producers, and a shipped consumer reads them through the data door (the config_changes view, pinned by the settings dogfood test as the admin);
    • none of them carries a record's field values (the per-writer measurement in audit-log-field-redaction.ts).
  • Excluded, fail closed: a record action (create, read, update, delete) that names no record, a record under an object the engine does not know, and a row naming the ledger itself.

Pins

Ablation: put the forbidden behaviour back, red, restore

Both mutations ran from committed state through scripts/ablation-replace.mjs, and each restore was proven: blob equals HEAD and git diff HEAD is empty.

Verification at HEAD d376985f

Every exit code was captured before any pipe.

Acceptance notes


Generated by Claude Code

claude added 5 commits October 1, 2026 14:40
…the caller can read

The compliance ledger takes the activity stream's parent-record read gate.
The gate's mechanism moves into parent-record-read-gate.ts, shared by
both streams; the ledger declares the rows that are about no record
(run-level events) outside the gate's class.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
…or serves on its own function

The ledger's record gate now excludes, for every non-system caller, the
rows about a deleted record and the rows naming a record it cannot
judge. The redaction's handling of those rows is pinned on
redactAuditLogRows over the row at rest; the read path pins that they
are not served.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
… data doors

A ledger reader is not served the rows about a record the data plane
answers it 404 for, through the list, by-id, query and grouped-count
doors; its own record's rows and a row about no record are served; the
admin is the control. The field-values pin serves the live record's rows
only, since the deleted record's rows now reach no non-system door.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
…text early return

Row 45 names the ledger's parent-record read gate beside the activity
stream's; the declared counts are regenerated by gen:system-context-census.
The changeset states the gate and its row classes.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-audit, touching 44 documentable anchor(s).

43 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 7c5a311a5829ae3b550a3eeb9bb984f06be8b865.

⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 3 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 — 9 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 7c5a311a5829ae3b550a3eeb9bb984f06be8b865 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 7c5a311a5829ae3b550a3eeb9bb984f06be8b865

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

claude added 4 commits October 1, 2026 18:35
…dger-parent-read-gate

# Conflicts:
#	content/docs/permissions/system-context.mdx
…owing it is, and its mount order

The changeset takes the activity stream gate's form: minor, a BREAKING
paragraph, Clause-② no (narrowing), the ADR-0087 not-required marker
and a Migration paragraph. The mount comment states the order on the
ledger after the query guard landed: query guard, field redaction,
parent-record read gate.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
… only to a caller who can read its record

The Reading-the-trail paragraph said ledger queries go through the data
service like any other object. They now take the ledger's parent-record
read gate: a view of a record the reader cannot open, or of one since
deleted, stays stored and is served only to system-context reads.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
…ubject user, as the ledger's parent-record gate requires

Under the ledger's parent-record read gate a reader is served the rows
about a user only when it can read that user. The pin's readers could
open only their own user row, so they were served none of the subject's
rows and the armed check disarmed. Each reader's sets now add view-all
on the user object (a row-scope grant; the field classes still apply,
measured by the armed check), and a new armed control asserts every
reader opens the subject through the data door. No assertion changed.

Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…mpts its holder from the parent-record read gate; platform administrators hold it by default (objectstack-ai#21260) (objectstack-ai#21296)

Fixes objectstack-ai#21260
Clause-②: yes (widening)

This executes ruling B on objectstack-ai#21175 (comment `5942331027`, maintainer
「同意264」). The compliance ledger gets an audit capability,
`view_all_audit_log`. A caller who holds it is exempt from the ledger's
parent-record read gate. Field-level narrowing (PR objectstack-ai#21171) still applies
to that caller. Platform administrators hold it by default. Every other
position holds it only by explicit grant. objectstack-ai#21175 is the parent card.
This PR does not close it (it is already closed).

**Status.** Draft. `Clause-②: yes`, so an isolated contract-review-tier
review is owed before enqueue. The PM runs it.

## Cross-lane surfaces, named before the change list

- **`packages/spec/src/security/capabilities.ts`** (`domain:spec`). One
new `PLATFORM_CAPABILITIES` entry. This is cross-lane, as the claim
declares.
- **`packages/spec/src/identity/eval-user.zod.ts`** (`domain:spec`).
This file holds the default grant to platform administrators.
- The claim expected the admin grant path to live in `plugin-security`.
Measured: it lives here. `ADMIN_FULL_ACCESS_CAPABILITIES` is the one
list that two consumers read:
    - plugin-security's `admin_full_access` set spreads it;
- core's configured-owner envelope (`resolve-authz-context.ts`) reads
it.
- Adding the capability anywhere else would fork that list. It is
reported here as the claim requires: "If that path lives outside
`plugin-security`, the build reports it before touching it."
-
**`packages/plugins/plugin-security/src/objects/default-permission-sets.test.ts`**.
Test only, no source change in `plugin-security`.
- Its pin on the admin set's exact capability list goes red on any
capability change, by design. The literal now names the new capability,
and its docblock says why.
  - A new pin says that no other shipped set carries the capability.

## What changed

1. **The declaration.** `PLATFORM_CAPABILITIES` gains
`view_all_audit_log`:
   - label "View All Audit Log", `scope: 'org'` (see "Scope" below);
   - its description names exactly what it lifts and what still applies.
- `PLATFORM_CAPABILITY_NAMES` derives from the list, so the seeder
(`bootstrapSystemCapabilities`), the authoring lint and the anchor floor
pick it up without a second edit.
2. **The default holder.**
`ADMIN_FULL_ACCESS_CAPABILITIES.systemPermissions` lists it. Platform
administrators hold it both ways:
   - through the `admin_full_access` grant;
   - through the configured-owner envelope.
   - There is no role-name test anywhere.
3. **The exemption.** `parent-record-read-gate.ts` is the shared
mechanism. It gains one declared field,
`ParentRecordGate.exemptCapability`, and one check at the top of
`computeParentRecordFilter`, before anything is scanned or ANDed in.
- The check reads the caller's resolved `systemPermissions`: the
capability set that the request's authorization resolver
(`resolveAuthzContext`, `assembleExecutionContext`) stamps on the
execution context.
- Every other runtime capability check outside plugin-security reads
that same set, for example the `sys_record_share` read scope and the
object-schema mask exemption. No new resolver is added.
- Only the ledger gate declares the field (`LEDGER_AUDIT_CAPABILITY` in
`audit-log-read-visibility.ts`). The activity stream's gate declares
none and says so.
4. **The anchor floor (`high-privilege.ts`).** No edit. That file's
platform floor is `PLATFORM_CAPABILITY_NAMES` itself, so declaring the
capability puts it on the floor.
- Reading: it unlocks ledger rows that the parent-record gate otherwise
withholds. So the anchor-laundering argument applies, and a set carrying
it must never bind to `everyone` or `guest`, even when an app declares a
capability of the same name.
- A pin in `high-privilege.test.ts` measures this. It refuses for
`everyone` and for `guest`, with the name declared.
5. **Docs.** `content/docs/permissions/record-view-auditing.mdx`, the
ledger reader sentence, which PR objectstack-ai#21194 wrote. It now names the
capability, who holds it by default, the field-level narrowing, and the
organization bound. Old and new text are in the report.
6. **Pins made false by this ruling.**
- `audit-log-parent-read-gate.dogfood.test.ts` said the admin is not
served a deleted record's rows. The admin is now a default holder: that
pin now asserts the reader is served none of them and the admin is
served both.
- The comment in `audit-log-field-values.dogfood.test.ts` is corrected
the same way.
7. **Changeset.** `@objectstack/spec` minor and
`@objectstack/plugin-audit` minor, carrying `Clause-②: yes (widening)`.

## Measured before the change (the gate as landed)

Real boot through the public data doors: `bootStack`, the real
SecurityPlugin, the SQL driver, REST, auth, and AuditPlugin's CRUD
mirror and auth-event sink.

- **At base `d2bc644f2`, with `dist` built from that tree.** A member
holding the ledger grant was served:
  - none of the deleted record's rows;
  - `404` on the sign-out row;
  - `404` on a private record's row;
- a total of 2 against 2,053 at rest on a broad read past the 2,000-row
bound.
- The platform administrator was served none of the deleted record's
rows. Its resolved capability set held the seven existing capabilities.
- **With only the new exemption line removed, so the gate is exactly as
objectstack-ai#21194 landed it.** Platform administrator and ledger-grant member,
both:
  - deleted-record list 0;
- `delete` row `404`, sign-out row `404`, ended session's sign-in row
`404`;
  - broad read total 2 against 2,054 at rest.
- This is A, as the dispatch expected.

## Scope: `org`, measured rather than assumed

- The dispatch default was `platform` unless the build measured that the
ledger is org-scoped. It is.
  - The registry provisions `organization_id` on `sys_audit_log`.
  - The CRUD mirror stamps each row with the record's organization.
- Measured on a real `isolated` boot, with two organizations each
created by its owner and the first owner granted the capability (the
readings below are row counts):

| Caller | Rows about the deleted record in its own org | Rows about the
other org's deleted record |
|---|---|---|
| Holder (org A owner) | 2 | 0 |
| Non-holder (org B owner) | 0 | 0 |
| Platform administrator | 2 | 2 |

- The capability lifts only the parent-record gate. The tenant wall
still bounds a holder to its own organization's rows. The platform
administrator reaches every organization through its own wall bypass,
not through this capability.
- This is now a pin, in the second block of the new dogfood file.

## Pins

| Pin | Where |
|---|---|
| A holder is served the deleted-record rows (list and by id), the
sign-out row, the ended session's sign-in row, a record it cannot open,
and a broad read past the bound whole. | dogfood |
| Field narrowing still applies to the holder: a withheld field's value
is absent from the deleted record's served snapshots, a plain field's
value is present, and the value is present at rest. | dogfood |
| A non-holder (ledger grant only) gets exactly A: none of these rows,
and a broad-read total below the bound. Its own record's rows are the
control. | dogfood |
| A platform administrator holds the capability by default, read through
`resolveAuthzContext`, and is served the deleted-record, sign-out and
broad-read rows. | dogfood |
| The activity stream's gate is not exempted for a holder or for the
admin. | dogfood; unit `activity-read-visibility.test.ts`; integration
`audit-log-read-visibility.integration.test.ts` (real SQLite driver) |
| The holder is bounded by its organization. | dogfood, second block |
| On every read operation the holder is not narrowed and is not
pre-scanned. Its broad read never meets the bound and logs no warning. A
caller with other capabilities, a non-list or none gets the gate
unchanged. The constant is pinned against `PLATFORM_CAPABILITIES` and
`ADMIN_FULL_ACCESS_CAPABILITIES`. | unit
`audit-log-read-visibility.test.ts` |
| The holder is served every row, deleted and unjudgeable rows included,
and the count matches. | integration |
| The capability is on the anchor floor. | `high-privilege.test.ts` |
| The admin grant carries it, declared `org`. |
`platform-admin-capabilities.test.ts` |
| Only `admin_full_access` among the shipped sets carries it. |
`default-permission-sets.test.ts` |

The pre-scan bound is pinned with a real fixture past it: 2,050 inserted
rows plus the real ones. The constant is not shrunk.

## Ablations

Each leg: mutate through `scripts/ablation-replace.mjs` (anchor must
hit), run the src-aliased plugin-audit suites, rebuild
`@objectstack/plugin-audit`, prove the mutation reached `dist/` with
`scripts/ablation-dist-preflight.mjs`, run the dogfood file, restore
with `git checkout HEAD -- ABSOLUTE_PATH` (blob hash equals HEAD and
`git diff HEAD` is empty), rebuild, and prove the restore in `dist/`. A
trap restores on EXIT, INT and TERM. Every leg turned red, as predicted.

| Leg | Unit and integration | Dogfood |
|---|---|---|
| Exemption removed | 3 red: 2 unit holder pins, 1 integration holder
pin | 8 red: every holder and admin pin, including the org-bound holder
and admin pins. Non-holder and activity pins stayed green. |
| Exemption applied to the activity gate | 2 red: unit activity pin,
integration activity pin | 1 red: the activity pin |
| Capability held by everyone | 19 red: the non-holder pin, plus every
pre-existing ledger narrowing case | 2 red: both non-holder pins |

The first run of the activity leg was a no-op. The replacement contained
its own anchor, and the tool refused it with "anchor count moved 1 to
1". Its readings are void. It was re-run with an anchor the replacement
does not contain, and the readings above are from that run.

## Gates and tests, at `d0a1903b7`

- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 110 families. All 110
exited 0.
- Two first answered `PREREQUISITE NOT MET` (exit 3) because sibling
packages had no `dist/`: `check:skill-examples` and
`check:dual-build-cjs-loads`. Both were re-run after building those
packages and exited 0.
- `--ran` reconciliation: "110 derived famil(ies) accounted for — 110
run, 0 NOT-MEASURED (a DERIVED zero — all 110 recorded an exit code and
none of them is 3)".
- **Spec artifacts.** `pnpm --filter @objectstack/spec check:generated`:
"All 15 generated artifacts are up to date". Nothing was regenerated,
because no artifact moves for one new list entry.
- **Tests.**
  - `@objectstack/spec`: 645 files, 18,316 passed.
  - `@objectstack/plugin-security`: 157 files, 3,407 passed.
  - `@objectstack/plugin-audit`: 35 files, 542 passed.
- `@objectstack/core`
`resolve-authz-context.platform-admin-config.test.ts`: 31 passed.
  - Dogfood, 4 ledger files at this head: 37 passed.
- **Typecheck.** `typecheck` passed for spec, plugin-audit,
plugin-security and dogfood, including each package's test-layer
typecheck.
- **Lint.** ESLint on the 14 touched TS files with `--no-inline-config
--format json`: 14 files linted, 0 errors, 0 warnings.
- The repo's `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`). So this diff cannot change the verdict on any
untouched file.
  - The full `pnpm lint` is left to CI.

## Acceptance notes

- **Agent principals (ADR-0090 D10).** The exemption reads
`systemPermissions` as the shared resolver stamped them. An OAuth agent
principal carries the delegating user's capabilities only when the user
consented to `actions:execute`, and none otherwise
(`assemble-execution-context.ts`). The `sys_record_share` read scope and
the object-schema mask exemption read the same set and follow the same
rule. This is a reading, recorded for the contract reviewer. It is not a
defect filed by this PR.
- **What a holder is served.** A holder is served rows about records of
objects it holds no object-level read on. Field-level security still
narrows the snapshots. This is the breadth "exempt from the
parent-record gate" means, and the capability's description and the docs
page both say so.
- **`PLATFORM_ADMIN_ONLY_CAPABILITIES` in plugin-security is
deliberately untouched.** The new capability is not a platform-admin
marker: it is org-scoped, and an explicit grant must not confer platform
standing.
- **Base.** `origin/main` has moved since `d2bc644f2`. The incoming
commits touch none of this PR's files, so they were not merged here. CI
and the queue validate the merge ref.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ion run proved stale (objectstack-ai#21339)

Docs-only checklist revision from the 17.6.0 release-verification run
objectstack-ai#21330 (subject `617f25f8`, Console pin `31971ff1e28f`). Every change
follows the checklist README's lifecycle rule: the item's `revision`
bumps and one `history` entry says what changed, why, and cites the run.
No product code, no `content/docs/**`, no generated file.

## Stale clauses the run proved (each a FAIL in objectstack-ai#21330 with disposition
stale-clause / assertion-defect)

| item | rev | evidence | changed by |
|---|---|---|---|
| `access-security.audit-log-browser` | 2 → 3 | admin `GET
/data/sys_audit_log?filter={"action":"delete"}` → 0 rows; the row is
stored with correct attribution | `30c530e5` (objectstack-ai#21194): the ledger serves
a non-system reader, admins included, only rows about records it can
read |
| `api-backend.filter-comparand-conformance` | 2 → 3 | POST `/query` →
400 `VALIDATION_FAILED` at `query.where.f_number.$eq`; GET `$filter` and
engine → 400 `INVALID_FILTER`; no door returns rows | objectstack-ai#20116 (`cfc3bcf1`
objectstack-ai#20247, `dd1b8031` objectstack-ai#20325) — the split query-contract-matrix rev 3
already records |
| `api-backend.date-range-preset-matrix` | 1 → 2 | equality
`{"signed_on":"today"}` → 400 `INVALID_FILTER` (temporal door);
`$gte:"this_week"` → 400 with `bareDateRangePresetComparandMessage` | by
design: `18.filter-preset-ordering-comparand-refused.ts` judges ordering
positions only |
| `records-forms.import-transform-matrix` | 1 → 2 | 400
`UNSUPPORTED_TRANSFORM` names the missing sandbox, 0 rows — but no
`framework#2611` | `f115b1f` (objectstack-ai#21188): refusals state decisions in
words, not tracker numbers |
| `studio-authoring.view-authoring-live` | 1 → 2 | `GET
/meta/view?object=repair_asset` serves `repair_asset.default` /
`repair_asset.form` with the authored config; container name 0 hits | by
design: `expandViewContainer` (objectstack-ai#7163, objectstack-ai#7736, objectstack-ai#13407) |

## Expected-fail notes 17.6.0 has made pass (clauses held in objectstack-ai#21330;
only their framing was stale)

| item | rev | measured | fixed by |
|---|---|---|---|
| `automation.packaged-flow-subflow-disable-refusal` | 1 → 2 | caller
off → child's disable retry 200, ledger `active=false`; caller-first
enable 409 `RESOURCE_CONFLICT` | `36d043b` objectstack-ai#20724, `0d9349f` objectstack-ai#20759; the
enable guard is `679f95e` objectstack-ai#20711 (step 6 now enables the child first) |
| `automation.packaged-flow-clone-contract` | 1 → 2 | clone survives a
cold restart and fires; still unreachable from Studio | durability
`cb4c31d` objectstack-ai#20907; reachability now filed as objectstack-ai#21332 (clause unchanged,
still expected to fail) |
| `access-security.packaged-flow-write-door-parity` | 1 → 2 | `PUT` /
`DELETE /automation/showcase_urgent_task_alert` → 403 `NOT_OVERRIDABLE`,
flow unchanged | `4b45afae` (objectstack-ai#20817); knownGap names the existing pin
`packaged-flow-write-door-parity.dogfood.test.ts` |

No clause was weakened: each still refuses the original failure mode
(rows returned, a served delete row, a 200-with-zero-rows), and the
clone clause keeps its expected fail.

## Validation

- `node scripts/check-platform-checklist.mjs` → `OK — 15 areas, 269
items (265 active, 2 planned)`; symbol anchors and line-citation sweep
green.
- `api-backend.json` is re-serialized in its existing canonical 2-space
form; the other four files are edited in place in their existing mixed
formatting.

Not in this PR (listed on objectstack-ai#21330's close-out instead): the other
checklist-accuracy findings the run collected, and the two `planned`
picklist items, which can only be promoted by a run in which they pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6

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

Projects

None yet

2 participants