Skip to content

docs(adr),refactor(plugin-security): restate the ADR text that names C3-retired security machinery; delete the unreachable audience-anchor binding gate - #23412

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-23403-adr-c3-restatement
Oct 14, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/issue-23403-adr-c3-restatement

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #23403
Clause-②: no

Restates the ADR text that still names security machinery C3 (#15204) has retired, and deletes one gate that has been unreachable since stage 8. A sub-card of #22975. It cites 7a's deletions (#23368) as done, so the seat lands it after 7a.

维护者速读(草稿)

改了什么

  • 修订五份 ADR(0138、0094、0126、0131、0103)中仍在描述已退役安全机制的文字,让每一处都与 main 当前行为一致。
  • 删除一个已经不可能被触发的安全检查(「受众锚点绑定闸门」),连同它的调用点和 3 个测试用例,共删约 120 行代码;并修正其他包里仍把它当作「在运行」来描述的注释。
  • ADR-0138 执行对照表里「锚点绑定层级」一行的状态,从 enforced 改为「everyone 已执行;guest 待后续 F4」。这是状态更正,不是新的决定。

为什么改

  • 阶段 8 下线了 sys_position_permission_set 表。这个闸门只检查对这张表的写入,此后再也不会执行,但 ADR 仍写着「由它负责拦截」,读者会误以为这层防护还在起作用。
  • 对 everyone 锚点,同样的高权限检查实际在「确认建议」和「重新推导」两处执行,ADR 现在如实写明。对 guest,目前没有任何入口能绑定权限集,ADR 也写明了这一点。

风险与代价(含回滚)

  • 运行行为没有任何变化:被删的闸门自阶段 8 起就没被调用过,删掉的也不是对外导出。
  • ADR 改动只是更正描述,不改变任何决定。
  • 回滚:revert 本 PR 即可,没有数据迁移。

席位意见

你要做的

审阅后批准合并(Tier H:改动了受管路径 docs/adr/**)。

What this changes

Code: @objectstack/plugin-security, patch.

  • SecurityPlugin.assertAudienceAnchorBindingGate is deleted, with its call in the security middleware and its three cases in security-plugin.test.ts. The two imports only it used (describeAnchorForbiddenBits, AnchorBindingContext) go too.
  • The gate returned on anything but a write to sys_position_permission_set. Stage 8 (feat(plugin-security)!: retire the four security-catalog row objects (#15204 stage 8) #23222) unregistered that object, so no write has reached the gate since.
  • The anchor tier itself is unchanged and still applied where a binding is made:
    • confirmAudienceBindingSuggestion and the everyone derivation (readAcceptedEveryoneSets), both in suggested-audience-bindings.ts;
    • for guest, no door binds a set.
  • Not an entry export. The changeset is .changeset/23403-audience-anchor-binding-gate-retired.md.

ADRs. Each item was checked first against a581c57eae (#23362), which recorded stage 8's retirement.

  • ADR-0138:

    • :171 drops the anchor to the deleted gate. It now says the guest tier has no caller on main until item 5's bind door.
    • Item 4 (:371) says where the predicate runs today: everyone on confirm and on every derivation; guest by the bind door when built.
    • Item 5's re-read note (:395) and the conformance row (:578) are restated the same way. The row now reads "enforced for everyone; guest: follow-up F4".
    • a581c57eae did not touch this ADR.
  • ADR-0094: a581c57eae already restated the TL;DR, D1–D4 and the addendum. Three lines still pointed at the module as living:

    • the RETIRED banner's "stays in the tree until stage 7a deletes it";
    • the unit-case citation of permission-set-projection.test.ts;
    • the Implementation line.

    All three now record the deletion by stage 7a (refactor(plugin-security)!: delete the permission-set projection, the seed-name lookup and the refusal sink (C3 stage 7a) #23368).

  • ADR-0126: "Three real mechanisms" gains a path note: stage 8 retired the sys_permission_set row whose active column carried the row state, and stage 7a deleted the module. This follows the ADR's existing path-note convention, and the path is kept as the record of what the section measured.

  • ADR-0131: both lists are dated evidence (re-verified at 2514d49f3), so they are annotated rather than rewritten.

    • The Consumers line (:59) gains a note: the five bootstrap seeders went with stages 6a and 6b, and permission-set-projection.ts with 7a.
    • The framework code list (:993) gains a note naming the four deleted seeder files and the projection.
  • ADR-0103 (:52): the provenance note said assertSystemRowWriteGate guards sys_sharing_rule, sys_position and sys_capability. Measured on main, its map (SYSTEM_ROW_PROVENANCE) holds sys_position and sys_capability only. sys_sharing_rule is deliberately ungated: plugin-sharing's provenance hook (sharing-rule-provenance.ts) stamps an edited row customized instead. And since stage 8 unregistered both objects, no write reaches that gate. The note now says this.

Acceptance notes

  • Out of scope, found: assertSystemRowWriteGate is unreachable for the same reason as the deleted gate, since its two objects are unregistered. It stays here because this card names only the anchor gate; it belongs with 8-post's residue or its own card.
  • Prose left for 8-post's comment sweep: two comments still describe the deleted gate:
    • the declared-capability-context.ts docblock ("all three runtime consumers", "the gate the engine middleware re-enforces on the insert");
    • examples/app-showcase/src/security/permission-sets.ts:360.
  • plugin-sharing/CHANGELOG.md names the gate as history and is not edited.
  • Comments that described the gate as live, now restated (comments and one note string only, no code):
    • the preview docblocks in security-plugin.ts (about :6151) and objectql/src/engine.ts (about :5518);
    • the engine's referential-integrity docblock (about :8859) and its sandbox gate list (about :20842);
    • integrity/dangling-reference-audit.ts (about :375), and the same phrase in two test comments (engine-lookup-referential-integrity.test.ts, dangling-reference-audit.test.ts);
    • lint/src/validate-security-posture.ts (about :37);
    • the position-active row's note in qa/dogfood/test/authz-conformance.matrix.ts (prose: no test reads note).

Verification

On 64c2e41049 (main dac99c2277's successor merged once, at bd13bf7808, to sit on 7a; not re-merged since):

  • check:adr-symbol-anchors and check:adr-links: exit 0.
  • Typecheck of plugin-security, objectql, lint and the dogfood package: exit 0.
  • authz-conformance.test.ts (the matrix row changed): 61/61.
  • On bd13bf7808, before this round's comment-only changes:
    • plugin-security suite: 185/185 files, 3,773 passed;
    • check:scripts-symbol-anchors and check:spec-docblock-symbol-anchors: exit 0.
  • On 59761d4a71: the derived gate sweep, 74 derived, 74 run, all exit 0, with --ran reconciling at 0 NOT-MEASURED and 0 UNRUN.

Generated by Claude Code

…C3-retired security machinery; delete the unreachable audience-anchor binding gate

SecurityPlugin.assertAudienceAnchorBindingGate guarded writes to
sys_position_permission_set alone, and C3 stage 8 unregistered that
object, so no write reached it. The gate, its middleware call and its
three unit cases are deleted, with the two imports only it used.

The ADRs that still named the retired machinery are restated to what main
does:
- ADR-0138: the anchor binding tier runs for everyone on confirm and on
  every derivation, and no door binds a set to guest until the bind door;
  the anchor to the deleted gate is dropped.
- ADR-0094: the projector's module and suite are recorded as deleted by
  stage 7a.
- ADR-0126: a path note on the deleted projection module.
- ADR-0131: notes on the consumer and code lists the C3 stages emptied.
- ADR-0103: the provenance note says which gate guarded which object, and
  that no write reaches it after stage 8.

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

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/objectql, @objectstack/plugin-security, touching 6 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/lint/src/validate-security-posture.ts, packages/objectql/src/integrity/dangling-reference-audit.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/automation/hook-bodies.mdx (via ObjectRepository (symbol, a top-level class))
  • content/docs/permissions/authorization.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/permissions/delegated-administration.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • 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-sets.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/permissions/positions.mdx (via sys_position (literal, a string literal in assertAudienceAnchorBindingGate), sys_position_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/permissions/system-context.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/plugins/packages.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/protocol/backward-compatibility.mdx (via sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/ui/forms.mdx (via SecurityPlugin (symbol, a top-level class))

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

  • content/docs/releases/implementation-status.mdx (via SecurityPlugin (symbol, a top-level class), sys_position_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/index.mdx (via sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v12.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v13.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position (literal, a string literal in assertAudienceAnchorBindingGate), sys_position_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v14.mdx (via sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v15.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v17/17-0.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v17/17-1.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate), sys_position (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v17/17-2.mdx (via sys_position (literal, a string literal in assertAudienceAnchorBindingGate), sys_position_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v17/17-5.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))
  • content/docs/releases/v17/17-6.mdx (via sys_permission_set (literal, a string literal in assertAudienceAnchorBindingGate))

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
  • 2 changed file(s) yielded no anchor (packages/lint/src/validate-security-posture.ts, packages/objectql/src/integrity/dangling-reference-audit.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 72 pages)
  • 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 — 30 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 c857a93ee42d0144b4643f49f899053ea099e200 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json c857a93ee42d0144b4643f49f899053ea099e200

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: bd13bf7808f0ce7e4833ff3a297a355e9d3d4471
Local-runs: none

PR #23412, card #23403 (Tier H, governed by docs/adr/**). The head is the PR's current head, bd13bf7808. It merges main 5656c5638a (which contains 7a, dac99c2277) into 59761d4a71, and it replaced 59761d4a71 while this review ran (dev comment 6162563147).

  • The net diff, origin/main...bd13bf7808 (merge base 5656c5638a), is 8 files, +54 / −142.
  • Its content matches e4ec6ec1a5..59761d4a71 exactly; only the index lines and hunk offsets differ.
  • The merge ref 18ff991d38 has the same tree as the head (empty diff).

Read for this record: card #23403, its claim 6161966159 and the dev report 6162484475; on #22975, the seat's answers 6161961558 and 8-post's report 6161864889; the PR body and its ## 维护者速读(草稿) section; a581c57eae; and the head's check-runs.

① Derived judgments

1. The gate: holds.

  • Unreachable on main. assertAudienceAnchorBindingGate returned at once for any object other than sys_position_permission_set.
    • Stage 8 (66949b7729) deleted objects/sys-position-permission-set.object.ts and dropped it from objects/index.ts. The only definition left on main is __tests__/legacy-catalog-objects.testkit.ts.
    • The engine's insert and update both call resolveObjectName before executeWithMiddleware. That call throws OBJECT_NOT_FOUND for a name the registry does not hold (packages/objectql/src/engine.ts). So no write to that object reaches the security middleware, and none reaches the gate.
  • Removed cleanly.
    • Removed: the call in the middleware, the method, its three unit cases, and the two imports only it used.
    • The three cases called the private method directly through (plugin as any), so they stayed green while the gate was dead.
    • On the merge ref, describeAnchorForbiddenBits and AnchorBindingContext have no use left in security-plugin.ts. readDeclaredCapabilityContext (still used by declareEveryone), PermissionDeniedError and bootstrapPermissionSets are still used.
    • A git grep of the merged tree finds no code caller and no anchor JSON. Only prose names the gate now (finding B).
    • Side effect, harmless: assertPackageManagedWriteGate's docblock now sits directly above its own method. Before, the anchor gate sat between them.
  • No ADR-0090 D9 guarantee is lost. The gate judged sys_position_permission_set rows, and no grant reader reads that table.
    • The guest's grants come from the guest position's catalog definition: resolveGuestAnchorGrants calls positionBoundSetNames in core/security/resolve-authz-context.ts.
    • everyone: describeAnchorForbiddenBits runs in two places. confirmAudienceBindingSuggestion refuses with a 403, and readAcceptedEveryoneSets withholds on every derivation. declareEveryone filters the baseline with describeHighPrivilegeBits, which is the everyone tier exactly (high-privilege.ts).
    • guest: nothing on main calls the guest tier, and no door binds a set to guest (the anchors' seal, plus the refusal of a guest row on confirm). So the tier holds only because nothing can bind. Follow-up F4 (the bind door) must call it.

2. The ADR edits: true of main, except one sentence (finding A).

  • ADR-0138. Edited: :171, item 4 (:372), item 5's re-read note (:400–402), and the enforcement-map row (:584).
  • ADR-0094. Edited: :19, :187 and :399. All three are true.
    • Stage 7a deleted permission-set-projection.ts and its suite; both are absent on main.
    • Stage 8 removed the wiring: the 66949b7729 hunk in security-plugin.ts drops registerPermissionSetProjection and reconcilePermissionSetProjection.
    • registerMutationProjector remains (protocol.ts :6736).
    • The banner edit completes a581c57eae's own dated RETIRED banner, which had predicted the deletion ("stays in the tree until stage 7a deletes it").
  • ADR-0126 (:97). A dated path note in the same form as the existing one at :103. True, and an insertion.
  • ADR-0131 (:66 and :1008). Dated insertions, and true.
    • At 2514d49f3 there were five bootstrap seeders. Each one's deletion is confirmed:

      Seeder Deleted by Commit
      bootstrap-builtin-positions.ts stage 6a 990341a93c
      bootstrap-declared-positions.ts stage 6a 990341a93c
      bootstrap-system-capabilities.ts stage 6b-1b 7aa8d51c0f
      bootstrap-declared-capabilities.ts stage 6b-1c 95a843573a
      bootstrap-declared-permissions.ts stage 6b-2 c8a9783eca
    • tenant-layer.ts, per-organization-catalog.ts and bootstrap-platform-admin.ts are present on main, as the note says.

    • The :1008 note names exactly the four seeders that appear in that code list.

  • ADR-0103 (:50–59).
    • The correction itself is right. SYSTEM_ROW_PROVENANCE holds only sys_position and sys_capability, and sharing-rule-provenance.ts stamps an edited row customized instead of refusing it.
    • But the edit adds one sentence that is false of main (finding A). The aside is also rewritten in place, with no date.
  • ADR prose still naming a retired module, outside this card's five ADRs (a581c57eae missed these too):
    • ADR-0068 :3, the Status line: "built-in identity rows seeded (bootstrap-builtin-positions.ts)". Stage 6a deleted that file, the boot writes no sys_position row, and the line has no Amended segment.
    • ADR-0086 :71 cites objects/sys-permission-set.object.ts as true "today". Borderline: its Status line's Amended segment covers the retirement.
    • I grepped docs/adr on the head for every file 6a, 6b, 8, 7b and 7a deleted. Every other hit is dated, carries a note, or sits in ADR-0131 D13's list of retirements.

3. Gates: green on the head.

  • check:adr-symbol-anchors: the Lint · docs & code guards step "ADR symbol anchors resolve (no line numbers survive)" succeeded.
  • check:adr-links: the Check Documentation Links step "Check ADR cross-links" succeeded.
  • check:adr-anchors: step 9 of Lint · repo & tooling gates succeeded.
  • The only symbol anchor that named the gate (ADR-0138 :171) is gone. Both anchors left on the changed lines still resolve: high-privilege.ts#describeAnchorForbiddenBits (:192) and delegated-admin-gate.ts#ANCHOR_POSITIONS (:151).

4. The 速读 draft: true of the diff, except for the gaps below.

  • 「让每一处都与 main 当前行为一致」 is not true while finding A stands.
  • 「不改变任何决定」 holds for decisions. But the draft does not say that the enforcement map moves the guest tier from enforced to follow-up F4. It says only that no door binds a set to guest, which is the substance.
  • 「约 120 行」 is fine: 124 lines of code are deleted.
  • Outside the 速读, the body's Verification section is stale. It still reads "On 59761d4a71 … not re-merged", but the head is now the re-merge bd13bf7808, recorded only in comment 6162563147.

Findings

  • A (blocking). ADR-0103 :53–54 is false of main.

    • The sentence: "On main today only sys_sharing_rule carries that column on a registered object."
    • Two other registered objects carry the same read-only managed_by select (platform / package / admin):
      • sys_email_template, declared at packages/platform-objects/src/audit/sys-email-template.object.ts :166 and registered by plugin-email/src/email-plugin.ts :371;
      • sys_webhook, declared at plugin-webhooks/src/sys-webhook.object.ts :269 and registered by webhook-outbox-plugin.ts :182.
    • The a581c57eae note two lines below already states the fact with the right scope: "of the three provenance-column objects only sys_sharing_rule remains".
    • Remedy: scope the sentence ("Of the three objects named, only sys_sharing_rule is still registered"), or drop the clause.
    • It blocks because the card's acceptance is that every line it names is true of main, and the maintainer approves this text on this record.
  • B (non-blocking). More prose still describes the gate as live. The PR hands only two comments to 8-post; these also remain:

    • security-plugin.ts :6151, in the same file: the preview docblock lists "the ADR-0090 D5/D9 audience-anchor binding guard" among the pre-resolution gates that run;
    • objectql/src/engine.ts :5518–5519, the same list;
    • engine.ts :8857–8859 and objectql/src/integrity/dangling-reference-audit.ts :375: "the audience-anchor gate has to resolve that permission set";
    • lint/src/validate-security-posture.ts :37: "held by the runtime's own describeAnchorForbiddenBits(set, 'guest') gate";
    • qa/dogfood/test/authz-conformance.matrix.ts :578, which names assertAudienceAnchorBindingGate as a live, unfiltered write gate.

    8-post was told to keep the gate (6161961558, item 1), so its sweep will not have taken these. They need a named carrier.

  • C (non-blocking). Two more gates are dead for the same reason. The PR names assertSystemRowWriteGate. Also unreachable: assertCuratedCapabilityNameGate (sys_capability only) and assertPackageManagedWriteGate (sys_permission_set only). 8-post's carry-over lists "the four gates"; the seat should confirm these two are among them.

  • D (note). Landing order. 8-post edits the lines next to the removed call and method in security-plugin.ts, so whichever PR lands second needs a re-merge.

② Semver level

patch agrees with the changeset (.changeset/23403-audience-anchor-binding-gate-retired.md, @objectstack/plugin-security: patch).

  • The gate was a private method of the exported SecurityPlugin, and the package's exports cannot reach it.
  • The two imports removed were internal.
  • No export or signature changes. Behaviour does not change either, because the code was unreachable.
  • Check Changeset succeeded on the head, and the changeset text is accurate.
  • Nothing under packages/spec/src/** is touched, so Clause-②: no is right. The review is owed only because governed text (docs/adr/**) changed.

③ Boundary flags

  • The dev's flags. open_questions and deviations are both empty, so there is nothing to answer.
    • Out-of-scope finding 1, assertSystemRowWriteGate, belongs with 8-post's residue. Agreed; see C.
    • Out-of-scope finding 2, the prose list, is incomplete; see B.
  • Boundaries. The diff crosses no repository boundary, no app/platform boundary and no published-contract boundary. Item 5 cites objectui's pin that a guest row offers no Accept; this diff does not affect it.
  • NOT MEASURED: dogfood and the CLI tier. Acceptable for this diff.
    • The engine refuses an unregistered object before the middleware runs, so no dogfood scenario could reach the removed code.
    • The CLI's organization-ownership ceremony reads the legacy bindings table outside the engine middleware.
    • CI's Dogfood Regression Gate succeeded on the head; its shards were skipped by the path filter.
  • Check-runs on bd13bf7808: all 40 completed: 34 success, 6 skipped (opt-in pack smoke, docs build, console pin, the dogfood shards, the CLI dogfood, temporal conformance), 0 red, none still running. That includes Test Core (1/6 to 6/6), Type Check · workspace and TypeScript Type Check.
    • The earlier head 59761d4a71 showed 7 cancelled runs and 1 failure. The failure was TypeScript Type Check: "type-check lane typecheck-workspace concluded cancelled". They are the concurrency cancellation from when the head moved, and none of them is on this head.

FAIL rests on finding A alone. If A is fixed and nothing else moves, a delta review of the new head should be able to pass it.

Adopted by domain:engine seat 2 at 2026-10-14T07:12Z, after a tool-use audit of the isolated reviewer's transcript (opus). The reviewer made reads, git fetch into review refs, REST GETs, and record writes only. It made no GitHub write and ran no build, test or gate. The seat takes the verdict as FAIL, on finding A only.

  • The fix: the dev restates the ADR-0103 sentence (about :53–54). It says only that, of the three objects it names, sys_sharing_rule alone is still registered. sys_email_template and sys_webhook also carry managed_by.
  • Then: a delta review on the new head, and the Tier H kit for the maintainer's approval.

Implemented-by: claude/issue-23403-adr-c3-restatement
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: FAIL

…bjects is still registered, not which objects carry the column

sys_email_template and sys_webhook also carry managed_by; the sentence
claimed sys_sharing_rule alone did. It now says only that, of the three
objects the passage names, sys_sharing_rule is the one still registered.

Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0f2d82aebdc78811b39b099d947ff204851324a7
Local-runs: none

PR #23412, card #23403 (Tier H by path, docs/adr/**). Served in-seat at the contract tier by the director seat (summon #37) on the maintainer's word 「23412 你帮我做一轮fable复审」, 2026-10-14T07:41Z. The head is the PR's current head 0f2d82aebd: 59761d4a71 (the change), bd13bf7808 (main 5656c5638a merged, which carries 7a dac99c2277), 0f2d82aebd (ADR-0103's sentence restated, the fix for finding A of the prior record 6162991283). This is the newest record naming this head; the prior record names bd13bf7808 and was adopted from a default-tier reviewer, so it does not stand for the current head on its own. Read for this record: the net diff (8 files, +54/−142), the card, the dev report 6162484475 and comment 6162563147, the prior record, ADR-0131 D10/D13, and the head's check-runs. Every claim below was re-read on origin/main 5656c5638 from this seat, independently of the prior record.

① Derived judgments

  1. The gate is dead on main and is removed whole. assertAudienceAnchorBindingGate returned unless opCtx.object === 'sys_position_permission_set'. No name: 'sys_position_permission_set' registration exists in packages/** on main (stage 8, 66949b7729, retired the four catalog row objects; the only definition left is a test kit), and the engine resolves the object name before the middleware runs, so no write reaches the gate. The diff removes the one call (security-plugin.ts :2528 on main), the private method (:6821–6890), its three direct-call cases, and the two imports with no other use in the file (describeAnchorForbiddenBits, AnchorBindingContext); readDeclaredCapabilityContext keeps its other use (:4779, declareEveryone). A grep of main finds no other code caller: the remaining names are prose (ADR-0138, examples/app-showcase/src/security/permission-sets.ts:360, qa/dogfood/test/authz-conformance.matrix.ts:578, a CHANGELOG). Correct.
  2. No ADR-0090 D9 guarantee is lost. describeAnchorForbiddenBits still runs where an everyone binding is made: suggested-audience-bindings.ts :512 (the derivation) and :865 (confirm), and at authoring time in lint/src/validate-security-posture.ts :777. The guest tier has no caller because no door binds a set to guest; that is the state security: no door can bind a permission set to the guest anchor — ADR-0138 D3 item 5 has no enforcer (PUT /meta/position/guest answers 403 NOT_OVERRIDABLE; guest suggestions are refused) #23267's ruling C (6159279205) left on purpose (the bind door is follow-up F4). ADR-0138's map row moving to "enforced for everyone; guest: follow-up F4" is the true status, not a new decision. Correct.
  3. ADR-0103 (:50–59), the fixed sentence. SYSTEM_ROW_PROVENANCE holds sys_position and sys_capability only (:797–); git log -S over the file's history shows sys_sharing_rule was never a key of that map, so "never sys_sharing_rule" is true; plugin-sharing/src/sharing-rule-provenance.ts stamps customized instead of refusing; of the three objects the aside names, only sys_sharing_rule is registered on main (sys-sharing-rule.object.ts:29). The sentence is now scoped to those three objects, so finding A of 6162991283 is fixed; sys_email_template and sys_webhook are no longer contradicted. Correct.
  4. ADR-0094 (:19, :187, :399). permission-set-projection.ts and its suite are absent on origin/main (7a, refactor(plugin-security)!: delete the permission-set projection, the seed-name lookup and the refusal sink (C3 stage 7a) #23368, merged); registerMutationProjector remains (protocol.ts:6736); the banner edit completes a581c57eae's own dated prediction. Correct.
  5. ADR-0131 (:66, :1008), dated insertions. The five seeders are deleted: bootstrap-builtin-positions.ts and bootstrap-declared-positions.ts by 990341a93, bootstrap-system-capabilities.ts by 7aa8d51c0, bootstrap-declared-capabilities.ts by 95a843573, bootstrap-declared-permissions.ts by c8a9783ec; tenant-layer.ts, per-organization-catalog.ts and bootstrap-platform-admin.ts are present. Correct.
  6. ADR-0138 (:171, item 4, item 5's note, the map row) and ADR-0126 (:97): at 7b926f7600 the gate existed and called the predicate (2 hits in security-plugin.ts at that commit); the dated path note follows the ADR's own convention. The ADR-0138 edits are in place and undated except by their citations; non-blocking, as the prior record said, and consistent with ruling C's amendment line already in item 5. Correct.
  7. Gates on the head: Lint · docs & code guards (ADR symbol anchors), Check Documentation Links (ADR cross-links), Lint · repo & tooling gates, Check Changeset, Type Check · workspace, TypeScript Type Check are green; the Test Core shards finished green before this record was written (counts below).
  8. The 速读 draft is true of this head: 「让每一处都与 main 当前行为一致」 holds now that A is fixed; 「不改变任何决定」 holds (one status row changes, no decision). Two lines for the seat's 终稿, non-blocking: the body's Verification section still names 59761d4a71 "not re-merged" while the head is 0f2d82aebd (two commits later, main merged); and the 速读 does not say that ADR-0138's map row moves the guest tier to follow-up F4.
  9. Carry-overs from 6162991283 stand and are non-blocking: B (the prose still naming the gate as live: security-plugin.ts :6151, objectql/src/engine.ts :5518 and :8857, integrity/dangling-reference-audit.ts :375, lint/src/validate-security-posture.ts :37, qa/dogfood/test/authz-conformance.matrix.ts :578, the showcase and the declared-capability-context.ts docblock), C (assertSystemRowWriteGate, assertCuratedCapabilityNameGate, assertPackageManagedWriteGate unreachable for the same reason), D (a re-merge for whichever of this PR and 8-post lands second). Under ruling 6162084315 ([Decision] 收班流程修法:清单补「释放认领 / 决策挂 needs-user-decision / 欠账必带卡号」,哪几步做成脚本检查 #23396, letter A) "the next PR that touches X" is not a carrier: the seat's ACCEPT names a card number for B and C, or files one. This session cannot search issues (the search API is off this session), so the seat confirms the carrier.

② Semver level

patch agrees with .changeset/23403-audience-anchor-binding-gate-retired.md (@objectstack/plugin-security: patch): a private method of the exported SecurityPlugin is deleted, no export or signature moves, no behaviour changes because the code was unreachable; Check Changeset is green on the head. Clause-②: no is right: nothing under packages/spec/src/** moves; the review is owed by the governed text face (docs/adr/**) alone.

③ Boundary flags

The dev raised no open_questions and no deviations. No repository, app/platform or published-contract boundary is crossed; objectui's pin that a guest row offers no Accept is cited, not touched. NOT MEASURED locally: dogfood and the CLI tier; acceptable, since the engine refuses an unregistered object before the middleware and the ceremony reads the legacy bindings outside the middleware; the head's Dogfood Regression Gate is green with its shards path-skipped.

Check-runs on 0f2d82aebd at this record: 40 completed: 34 success, 6 skipped (Build Docs, Console Pin Gate, the Dogfood Regression Gate shards, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL)), 0 red, none running; the six Test Core shards, Type Check · workspace and TypeScript Type Check among the green.

Implemented-by: claude/issue-23403-adr-c3-restatement
Reviewed-by: session_01LqNgdPZB2Jnr5HpGbaqLeB

VERDICT: PASS

Tier H: the maintainer's approval lands it. Nothing further is owed by the dev; the two 终稿 lines above are the engine seat's.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0f2d82aebdc78811b39b099d947ff204851324a7
Local-runs: none

Delta review of PR #23412, card #23403 (Tier H, governed by docs/adr/**). The previous record, 6162991283, failed bd13bf7808 on finding A alone.

Read for this record:

  • the record 6162991283;
  • the PR body and its ## 维护者速读(草稿) section, unchanged since that record;
  • the dev's patch comment 6163018199 on the card;
  • the delta bd13bf7808..0f2d82aebd and the net diff origin/main...0f2d82aebd;
  • the check-runs on the head.

① Derived judgments

1. Finding A: fixed. The new sentence is true of origin/main.

  • The delta replaces two lines of ADR-0103 (:53–54).
    • Before: "On main today only sys_sharing_rule carries that column on a registered object".
    • After: "Of the three objects named here, only sys_sharing_rule is still registered on main".
  • Each part, checked on origin/main b5b1bf7277:
    • "The three objects named here." The aside names sys_sharing_rule, sys_position and sys_capability (:51–52) and no other object before this sentence.
    • sys_sharing_rule is registered. plugin-sharing/src/sharing-plugin.ts :585 lists SysSharingRule in the plugin's objects. The object declares managed_by at sys-sharing-rule.object.ts :249.
    • sys_position and sys_capability are not registered. Stage 8 (66949b7729) deleted sys-position.object.ts and sys-capability.object.ts and dropped them from objects/index.ts. On main, both are defined only in plugin-security/src/__tests__/legacy-catalog-objects.testkit.ts (:26, :147). A whole-tree grep outside test files finds no other definition.
    • "Still" is right, because both were registered until stage 8.
  • The other two objects no longer contradict it. The sentence no longer says which objects carry the column. sys_email_template (declared at platform-objects/src/audit/sys-email-template.object.ts :166, registered at plugin-email/src/email-plugin.ts :371) and sys_webhook (sys-webhook.object.ts :269, webhook-outbox-plugin.ts :182) both carry managed_by, and the sentence makes no claim about either.
  • The rest of the sentence did not change and is still true. sharing-plugin.ts :841 binds bindRuleProvenanceStamp. Its beforeUpdate hook sets customized: true on a non-system update of a package or platform row, and it never refuses the write.
  • It now matches the dated note. The 2026-10-14 note at :61–66 says the same thing ("of the three provenance-column objects only sys_sharing_rule remains"). The repetition is harmless.
  • Form: non-blocking. The fix is another undated edit made in place inside the aside, which the previous record already accepted. The Status line's 2026-10-14 Amended segment covers it.

2. The delta changes nothing else.

  • git diff bd13bf7808 0f2d82aebd touches one file, +2 / −2: ADR-0103 :53–54 only.
  • The net diff, origin/main...0f2d82aebd, is still 8 files, +54 / −142. Compared with the previous net diff, only those two lines and the ADR-0103 blob index differ.
  • Two lines replace two, so every line number cited in 6162991283 still points at the same text.
  • Outside .claude/, main is byte-identical between 5656c5638a and b5b1bf7277. So every fact 6162991283 measured on main reads the same today.

3. Everything the previous record judged right still holds.

  • The code, the other four ADR edits, the changeset and main's tree outside .claude/ are all unchanged. So these judgments in 6162991283 stand:
    • the gate: unreachable, removed cleanly, and no ADR-0090 D9 guarantee lost;
    • the ADR-0138, ADR-0094, ADR-0126 and ADR-0131 edits, all true of main;
    • ② and ③.
  • Spot-checked on the head:
    • assertAudienceAnchorBindingGate is named only in examples/app-showcase/src/security/permission-sets.ts :360 and qa/dogfood/test/authz-conformance.matrix.ts :578, both already in finding B.
    • security-plugin.ts no longer names describeAnchorForbiddenBits or AnchorBindingContext.
    • sys_position_permission_set is defined only in the testkit (:449).
  • Findings B, C and D stand as written, all non-blocking. The delta did not touch them and was not asked to. B and C still need a named carrier (8-post or a card of their own).
  • Outside the five ADRs, ADR-0068 :3 and ADR-0086 :71 are unchanged and stay as noted.

4. Gates and check-runs.

  • check:adr-symbol-anchors: green. Step 32 of Lint · docs & code guards, "ADR symbol anchors resolve (no line numbers survive)", succeeded on 0f2d82aebd.
  • check:adr-links: green. Step 3 of Check Documentation Links, "Check ADR cross-links", succeeded.
  • check:adr-anchors: green. Step 9 of Lint · repo & tooling gates succeeded.
  • The delta adds no anchor or link. The two new lines hold code spans only. This matches the exits the dev reported in 6163018199.
  • Check-runs on 0f2d82aebd: complete, none red. Read at 2026-10-14T07:40Z: all 40 completed, with 34 success, 6 skipped and none still running.
    • Success includes Test Core (1/6 to 6/6, then the aggregate at 2026-10-14T07:40Z), Type Check · workspace, TypeScript Type Check, Governed Surface Queue Guard and Check Changeset.
    • The 6 skipped are the same six as on bd13bf7808: the opt-in packed-tarball smoke, Build Docs, Console Pin Gate, the dogfood shards, Dogfood Verify CLI and temporal conformance.
    • The commit-status API has no statuses on the head.
    • The PR is still a draft, auto-merge is not armed, and GitHub reports it clean.

5. The 速读 draft: true of the final diff.

  • 「让每一处都与 main 当前行为一致」 now holds. With A fixed, every line the PR edits in the five ADRs is true of main.
  • 「共删约 120 行代码」 is fine: 124 lines of code are deleted (87 in security-plugin.ts, 37 in its test) and 1 added.
  • 「运行行为没有任何变化」 and 「删掉的也不是对外导出」 are true.
  • 「Tier H:改动了受管路径 docs/adr/**」 is true. docs/adr/** is the only governed path in the file list.
  • 「不改变任何决定」 is true of decisions. The earlier gap remains: the draft does not say that ADR-0138's enforcement-map row moves the guest tier from enforced to follow-up F4. Non-blocking.
  • The dev's claim in 6163018199 is true: the body and the 速读 make no claim about which objects carry the column. The body's ADR-0103 bullet describes the gate's map, and it is still accurate.
  • Stale, outside the 速读 (non-blocking). The body's Verification section still reads "On 59761d4a71 … not re-merged", but the head is now 0f2d82aebd, two commits later. The exits for bd13bf7808 and 0f2d82aebd appear only in card comments 6162563147 and 6163018199.
  • ### 席位意见 is empty, for the seat to fill.

② Semver level

Still patch. The delta is ADR prose only.

  • The changeset (.changeset/23403-audience-anchor-binding-gate-retired.md, @objectstack/plugin-security: patch) is unchanged, and Check Changeset succeeded on the head.
  • Nothing under packages/spec/src/** is touched, so Clause-②: no is still right.

③ Boundary flags

  • No new flag. The delta changes one prose sentence and crosses no boundary.
  • NOT MEASURED: dogfood and the CLI tier. Still acceptable, for the reasons in 6162991283. The delta adds no code. Dogfood Regression Gate succeeded on the head, and its shards were skipped by the path filter.
  • The head is one commit behind main. That commit touches only .claude/, which does not overlap this PR, so no re-merge is needed for this review. Landing order D still applies with 8-post.

PASS:

  • finding A is fixed with a sentence that is true of main;
  • the delta changes nothing else;
  • every judgment 6162991283 got right still holds;
  • the gates are green, and the check-runs are complete with none red.

Findings B, C and D and the 速读 gap stay non-blocking. Before the seat asks the maintainer to approve, it should:

  • fill ### 席位意见;
  • refresh the body's Verification section to 0f2d82aebd.

This PR is Tier H. This record does not approve it: only the maintainer's authorized approval lifts the landing.

Adopted by domain:engine seat 2 at 2026-10-14T07:43Z, after a tool-use audit of the isolated reviewer's transcript (opus). The reviewer made reads, git fetch, REST GETs, and record writes only. It made no GitHub write and ran no build, test or gate. The seat takes the verdict as PASS.

Implemented-by: claude/issue-23403-adr-c3-restatement
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

…deleted audience-anchor binding gate as live

The security-plugin and engine preview docblocks listed it among the
pre-resolution gates; the engine's and the dangling-reference audit's
comments (and their two tests' comments) said it has to resolve a bound
permission set; the lint rule said the runtime gate holds a guest binding;
the authz conformance matrix's position-active note named it among the
unfiltered write gates. Each now says the gate retired with
sys_position_permission_set in C3 stage 8, or drops it. Comments and one
note string only.

Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 · PR #23412(#23403:ADR 文字更正,删除一个失效的安全闸门)· domain:engine 2 号席 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-14T07:53Z

改了什么

  • 修订五份 ADR(0138、0094、0126、0131、0103)里仍在描述已退役安全机制的文字,让每一处都和 main 当前的行为一致。
  • 删掉一个早已不可能被触发的安全检查「受众锚点绑定闸门」(assertAudienceAnchorBindingGate),连同它的调用点和 3 个测试用例。
  • 五个包里仍把它说成「在运行」的注释一并改正。共 14 个文件,+79 / −162 行。
  • ADR-0138 执行对照表里「锚点绑定层级」那一行的状态,从 enforced 改为「everyone 已执行;guest 待后续 F4」。这是状态更正,不是新的决定。

为什么改

  • 阶段 8(S8)下线了 sys_position_permission_set 表。这个闸门只检查对这张表的写入,此后再也不会执行。
  • 但 ADR 还写着「由它负责拦截」,读者会以为这层防护还在起作用。
  • 这次更正连同 C3 第 7a、7b、8 阶段删掉的模块,一次性把 ADR 和代码注释对齐。

风险与代价(含回滚)

  • 运行行为: 没有任何变化。被删的闸门自 S8 起就没被调用过,也不是对外导出(plugin-security 记 patch)。
  • ADR: 只更正描述,不改变任何决定。
  • 回滚: revert 本 PR 即可,不涉及数据。

席位意见
建议批准。依据:

  • opus 契约审查第一轮 FAIL(6162991283),只有 ADR-0103 里一句话不实(说只有 sys_sharing_rule 带 managed_by 列,其实还有两张表也带)。修正后增量审查 PASS(6163455371)。
  • 审查顺带指出的过时注释已经补改。席位核对过,这次推送只改了注释,外加一条测试矩阵里的说明文字。
  • ACCEPT 6163620432。
  • 和 main 无冲突,新提交的 CI 正在跑,必查项全绿后席位才落地。

要你批准,只是因为改了受管路径 docs/adr/**。

你要做的
用 os-zhuang 或 hotlong 批准本 PR。批准后,席位在 CI 全绿时转 ready、送进合并队列。它和 8-post 改的是 security-plugin.ts 里相邻的几行,后落地的那个合一次 main 即可。

This was referenced Oct 14, 2026
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/m tests tooling

Projects

None yet

3 participants