Skip to content

refactor(objectql)!: retire the PLATFORM_OBJECT_TENANCY ledger — the tenant-audit mute is decided by the schema, and the [tenant-audit] warning names both readings - #25210

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25201-retire-platform-object-tenancy
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25201-retire-platform-object-tenancy

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #25201

Part of the ADR-0131 18.x line (owed scope of #15212, D13). Takes #25203's item 4 (the stale "tracked as #14570" docblocks).

Clause-②: no (narrowing)
Metadata-protocol: none

What changes

  • The per-table ledger is retired. packages/objectql/src/tenancy/platform-object-tenancy.ts is deleted, along with its six exports from @objectstack/objectql: PLATFORM_OBJECT_TENANCY, classifyPlatformObjectTenancy, isPlatformObjectOutOfTenantAuditScope, tenantScopedPlatformObjects, PlatformObjectTenancy and PlatformObjectTenancyEntry.

  • The tenant-audit mute is decided by the schema. In Engine.buildDriverOptions, an elevated write is auto-muted (bypassTenantAudit: true) only when the object has no tenant column (resolveTenantFieldName(schema) === null, i.e. tenancy.enabled: false, systemFields.tenant: false or no tenant field) or is federated. No name and no list decides it. That is ADR-0131 D9's "column ⇒ in scope", now applied to the audit as well.

  • The [tenant-audit] warning (SqlDriver.auditMissingTenant) no longer tells every unscoped write to "Pass tenantId". It names both readings, because the driver cannot tell them apart:

    • a write that belongs to one organization should run in that organization's context;
    • a write that crosses organizations on purpose (a platform admin the organization wall exempts, or a system job over every organization) needs no fix, and the caller declares it with bypassTenantAudit: true.

    The line's prefix and metadata are unchanged.

  • Docblocks: system-write-organization.ts, approval-service.ts, business-unit-graph.ts and the B3 test comment now describe the post-ADR-0131 write paths. Seed replay (C1) and elevated inserts (D9) stamp the organization, so a NULL member row is pre-18.0 residue or a lean composition. They no longer cite the ledger, nor sys_business_unit_member is unadjudicated in PLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 as the tracking card (that card is no longer open).

  • The census is repurposed, not removed. scripts/platform-object-tenancy-census.mjs already executed the schema predicate (resolveTenantFieldName on the registered schema) and never read the ledger. Two things move:

    • its HEADER pointer now targets resolveTenantFieldName's docblock in system-write-organization.ts;
    • the artefact's comment says reach: in is the whole of the engine's tenancy scope.

    The artefact is regenerated (the comment only; no row moved: 84 / 41 / 43). lint.yml's step runs it unchanged and green, so no governed-path change is needed. Its stated readers: that gate, and packages/cli/src/utils/organization-ownership-inventory.ts.

Measured before the change (base 9c431bad)

  • Readers of isPlatformObjectOutOfTenantAuditScope: one behavioural reader, engine.ts's isTenantAuditInScope. It only decided opts.bypassTenantAudit, which the SQL driver reads in auditMissingTenant to skip a WARN. Diagnostics only. The system-insert rule had no ledger reader left (stage 8b and feat(objectql,cli,plugin-webhooks)!: 18.x step 4: the walled lift (8b walled half, the os serve lift, the declared-webhook name guard, declared webhooks deployment-wide) #24667).
  • Importers of the six exports outside @objectstack/objectql:
    • scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs, a dated one-off audit that imports dist. Nothing runs it (no package.json script, no workflow, no gate roster). It now refuses loudly at import, which its own docblock prescribes for a missing ledger.
    • No package imports them.
    • Because they are published names, removing them is still declared a narrowing.

Acceptance notes

git grep PLATFORM_OBJECT_TENANCY, outside CHANGELOGs, still finds files this PR may not or should not touch:

  • .claude/skills/pm-dispatch/references/platform-readings.md:403 and docs/adr/0131…:878 (governed);
  • docs/audits/2026-09-tenancy-ledger-namespace-regexp-census.md (a dated audit record);
  • scripts/audits/14096-…mjs (above).

Also outside this card's surface: packages/spec/src/automation/schedule-organization.zod.ts:76 (and its generated reference page) still cites the deleted file's path.

Proof (head aaa6ef6a)

  • CI: every required context is green: Lint & Repo Gates, TypeScript Type Check, Test Core (all six shards), Build Core and Governed Surface Queue Guard, plus Check Changeset.

  • Pins, with ablations (the commit was in place first; each restore is git checkout HEAD -- <path>, and the tree was clean afterwards):

    • The mute is decided by the schema. tenancy-by-object-classification.test.ts passes 28/28. Ablation: I re-added a per-table exemption for sys_audit_entry in buildDriverOptions. Exactly 2 pins went red: "sys_audit_entry … is NOT auto-muted" and "the SAME driver options — the name decides nothing". The other 26 stayed green, among them every write pin (derive, refusals, the stamped tenantId) for sys_audit_entry itself. So a per-table entry changes no write; it changes only the mute.
    • The ledger's exports are gone. A tombstone pin checks that the public API has no PlatformObject* name.
    • The warning's new text. sql-driver-tenant-audit-posture.test.ts passes 12/12. Ablation: restoring base sql-driver.ts turns exactly the new pin red (1 failed, 11 passed).
  • Package suites and typecheck, all exit 0:

    • objectql: 8643 passed;
    • driver-sql: 3749 passed, 212 skipped (live-dialect cells);
    • plugin-approvals: 1213;
    • plugin-sharing: 1200;
    • service-messaging: 628;
    • service-storage: 1130.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 100 commands, each run with its exit code recorded before any pipe. --ran: "100 derived famil(ies) accounted for — 99 run, 1 NOT-MEASURED". Two exits are non-zero, both shallow-clone prerequisites:

    • check-plugin-teardown-shape --self-test: exit 3, prerequisite not met;
    • check-engine-split-ratio --days 90: exit 2, refuses on a shallow clone.

    The census gate is green in both modes: 84 objects, 41 in reach, 43 out, every exclusion explained.

  • check-governed-merges --branch: NOT governed. +263/−501 over 14 files.

  • R1 (fresh boot): pnpm dev -- --fresh gave health 200. The seeded admin signs in (200) and reads showcase_account (200, 14 rows). There are 0 [tenant-audit] lines, since single does not audit.

  • R4 (walled os serve):

    • Setup: OS_TENANCY_POSTURE=isolated with a fresh SQLite file, OS_SECRET_KEY, OS_AUTH_MEMBERSHIP_POLICY=invite-only and OS_PLATFORM_OWNER_EMAIL. The showcase declares no @objectstack/organizations, so for this run only I declared it locally and linked the workspace package. Both are reverted, and the tree is clean.
    • Result: health 200, no refusal. The owner signs up (200), signs in (200) and reads sys_user (200).
    • What the audit printed: 43 [tenant-audit] lines over boot and sign-up, every one with the new text, across 22 objects. 18 are showcase_* app objects, which were audited before too. The 4 platform objects now audited because the ledger's mute is gone are sys_activity, sys_business_unit, sys_member and sys_user_permission_set.
  • Upgrade harness: packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts passes 15/15. That is 8 tests on the 18.0 fixture (ceremony, boot, data) plus 7 on the fixture-18.0.0 leg (boots HEAD with no refusal, no destructive drift, rows intact, the D1 advisory).

Observation, not filed

On R4 several of those lines are creates. They must carry organization_id on the row: under a wall an organization-less system insert is refused (D9), and a row that names its organization reaches the driver with no tenantId (pinned in tenancy-by-object-classification.test.ts). Examples are create on sys_business_unit and bulkCreate on showcase_account. For an insert the row's own column is the scope, so these lines are noise. That was already true for application objects before this PR; it now covers those 4 platform objects as well. A follow-up could have the driver skip the audit on an insert whose row carries its tenant field. That is the 8c-1 design space, outside this card.


Generated by Claude Code

…enant-audit mute is read off the schema (WIP)

Claude-Session: https://claude.ai/code/session_01DWpoqyLrN4CEQFhSjFddQ9
Co-authored-by: Claude <noreply@anthropic.com>
…er's retirement; changeset

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/driver-sql, @objectstack/objectql, @objectstack/plugin-approvals, @objectstack/plugin-sharing, touching 20 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/tenancy/system-write-organization.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

25 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 e5f194ba13d8d9a2818c1ece98a9c206fa0c7623.

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

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

Coarse fallback — 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 e5f194ba13d8d9a2818c1ece98a9c206fa0c7623 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 e5f194ba13d8d9a2818c1ece98a9c206fa0c7623 → 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: aaa6ef6aa5ab46a82ba247dd58d6a2f92a304626
Local-runs: none
Scope: packages/drivers/driver-sql/src/sql-driver-tenant-audit-posture.test.ts, packages/drivers/driver-sql/src/sql-driver.ts, packages/objectql/src/engine.ts, packages/objectql/src/index.ts, packages/objectql/src/tenancy-by-object-classification.test.ts, packages/objectql/src/tenancy/platform-object-tenancy.ts (deleted), packages/objectql/src/tenancy/system-write-organization.ts, packages/plugins/plugin-approvals/src/approval-service.ts, packages/plugins/plugin-approvals/src/business-unit-member-org-screen.test.ts, packages/plugins/plugin-sharing/src/business-unit-graph.ts, scripts/check-platform-object-tenancy-census.mjs, scripts/platform-object-tenancy-census.json, scripts/platform-object-tenancy-census.mjs + .github/workflows/lint.yml (the census step, origin/main), package.json (the two census script entries), docs/adr/0131-total-organization-ownership-no-null-organization-id.md (D9, D13, lines 56 and 1249), docs/adr/0087-metadata-protocol-upgrade-contract.md (the runtime-export-retired category), scripts/check-adr-0087-registration.mjs (CATEGORIES), scripts/check-changeset-no-major.mjs (the none-at-patch rule), packages/cli/src/utils/organization-ownership-inventory.ts (the census's second reader), scripts/tenant-audit-census.mjs, scripts/check-tenant-audit-census.mjs, content/docs/permissions/tenant-audit-census.mdx, packages/services/service-messaging/src/delivery-claim-tenant-audit.integration.test.ts (the one external matcher of the warning), scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs (the one external importer), packages/spec/src/automation/schedule-organization.zod.ts, packages/spec/src/data/driver.zod.ts, .claude/skills/pm-dispatch/references/platform-readings.md, objectui at the pinned .objectui-sha cd14d8eeffe1

Read as: card #25201 (body, both comments), the reconciliation note 6339761793 on #15212, ADR-0131 D9/D13 on origin/main, PR #25210's body and file list, the net diff 9c431ba..aaa6ef6 (14 files, +263/-501; merge-base re-confirmed against origin/main 15764cd), and the check-runs on the head. Check-runs, latest run per name (41 names): every one is success or skipped. 18.x upgrade harness (opt-in) success 12:54:44Z (the needs:upgrade-harness lane; its first run was the skip before the label), Check Changeset success 12:50:52Z (four runs, all success), Lint & Repo Gates, TypeScript Type Check, Test Core 6/6, Build Core, Governed Surface Queue Guard, Dogfood Regression Gate, Check Documentation Links success. Part-of PR must not also close its card failed once on the first body (closing keyword) and is success on the reworded body. Scope note: the brief's floor named packages/plugins/plugin-sharing/src/business-unit-member-org-screen.test.ts; the PR's file is packages/plugins/plugin-approvals/src/business-unit-member-org-screen.test.ts and plugin-sharing has no file of that name on the list — reviewed at the real path.

① Derived judgments

  1. Removed published exports — RIGHT. The index.ts hunk drops six names from @objectstack/objectql's barrel: PLATFORM_OBJECT_TENANCY, classifyPlatformObjectTenancy, isPlatformObjectOutOfTenantAuditScope, tenantScopedPlatformObjects, and the types PlatformObjectTenancy, PlatformObjectTenancyEntry. Importers, measured on origin/main by git grep (CHANGELOGs excluded): inside the package, engine.ts (the mute) and the classification test, both rewritten in this diff; outside the package, exactly one, scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs, a dated one-off that imports the built dist and that no package.json script, workflow or gate roster runs. Every other hit is prose: ADR-0131 lines 56, 878 and 1249, the 2026-09 audit record, platform-readings.md:403, the spec zod docblock and its generated mdx. objectui at the pinned .objectui-sha cd14d8eeffe1 carries zero references, so the Console Pin Gate cannot be broken by the removal (Post-Task Add Changesets and GitHub Actions automation #4 holds). isPlatformNamespaceObject stays exported, and the new tombstone pin's regex (PlatformObject or PLATFORM_OBJECT) does not match it — right. The cloud repository is outside this brief's inputs and was not measured; the changeset's BREAKING banner and six FROM → TO lines are its carrier.

  2. The mute's new structural rule — RIGHT, and it does NOT mute exactly what the ledger muted; it must not. At base the audit was in scope when: tenant column, not federated, and not (platform-namespace object whose ledger verdict was anything but tenant-scoped). At head: tenant column and not federated. The ledger admitted nine objects (sys_file, sys_upload_session, sys_approval_request, sys_approval_action, sys_approval_approver, sys_automation_run, sys_notification, sys_notification_delivery, sys_record_share) and muted every other platform-namespace object by absence. Against the head's census (84 objects, 41 in reach, 43 out) the mute set shrinks by 32 platform objects that carry a tenant column (sys_activity, sys_approval_delegation, sys_approval_token, sys_attachment, sys_audience_binding_suggestion, sys_business_unit, sys_business_unit_member, sys_comment, sys_comment_reaction, sys_email, sys_email_template, sys_http_delivery, sys_import_job, sys_inbox_message, sys_invitation, sys_mail_association, sys_mail_message, sys_mailbox, sys_member, sys_notification_preference, sys_notification_receipt, sys_notification_template, sys_scim_connection_credential, sys_setting, sys_setting_audit, sys_share_link, sys_sharing_rule, sys_team, sys_user_permission_set, sys_user_position, sys_user_preference, sys_webhook), plus any cloud_ or ai_ object with a tenant column that this tree cannot see. What still mutes: the 43 reach-out objects (managedBy better-auth, systemFields.tenant false, tenancy.enabled false) and federated objects; an explicit caller value is still honoured, the branch only fills undefined. Is anything a write depends on inside the mute set? Readers of bypassTenantAudit at head (git grep, tests excluded): the SQL driver's auditMissingTenant early return at sql-driver.ts:15999 is the ONLY behavioural reader; the rest are the engine's pass-through allow-list (engine.ts:607), spec's DriverOptions schema (described as diagnostics-only), the hook-api type, and callers that set the option. No memory or mongodb driver reads it. So the mute decides one WARN line and nothing a write touches — right. The pins read consistently with the dev's ablation, which I did not re-run: the it.each "NOT auto-muted" pin for sys_audit_entry and the "SAME driver options" pin against dispatch_order are exactly the two a per-table re-entry would red; the derive and refusal pins never read the option.

  3. Write paths — RIGHT, unchanged in every posture. The engine.ts hunks are the import removal, the isTenantAuditInScope expression and comments; resolveSystemInsertOrganization is untouched, so D9 derivation under single and refusal under group and isolated are as at base. The sql-driver.ts hunks are a docblock and the warn string; applyTenantScope, injectTenantOnInsert and computeTenantField are untouched. Under a walled posture the only observable delta is WARN lines on elevated writes that reach the driver with no tenantId on the 32 objects above.

  4. The [tenant-audit] warning's new text — RIGHT. The prefix [tenant-audit] OP on tenant-scoped object "OBJECT" without options.tenantId and the metadata object (object, op, tenantField) are byte-identical, so the one external matcher found, service-messaging's delivery-claim integration test at line 64, still matches. The tail drops "Pass tenantId" — which, under D8, is the one change that would break a wall-exempt admin's cross-organization update — and names both readings with a remedy each. The runtime string carries no tracker number (check:doc-authoring is inside the green Lint lane). Two caveats, neither blocking: (a) "the row's own organization column does not scope an update or a delete" is true of the driver and silent about creates, where the line also fires (the create and bulkCreate call sites); noise on an insert whose row carries its organization is pre-existing for application objects and now reaches the 32 platform objects too — the dev's own observation, handled in ③; (b) a wall-exempt admin's REST write has no caller who can declare bypassTenantAudit:true, since the engine sets the mute only for isSystem, so that line keeps firing under a wall; the text names the reading honestly, and moving the declaration into the engine is 8c-1 design space, not this card.

  5. The census tooling's new meaning — RIGHT, and lint.yml's step still checks something real. The generator never read the ledger: it executes resolveTenantFieldName and resolveInjectedSystemColumns on every packages/**/*.object.ts. The change is prose in lockstep: HEADER moves from the deleted ledger to system-write-organization.ts, whose new resolveTenantFieldName docblock carries all three HEADER_MARKERS (the artefact path, the predicate name, "registered schema"); the artefact's $comment and the generator's ARTEFACT_COMMENT move together, so verdict 1 (byte-equal render) holds, and no row moved (84/41/43). The new meaning: reach-in is the whole of the engine's tenancy scope, system-write rule and audit alike. The lint.yml step "Platform-object tenancy census matches the tree" (continue-on-error, folded into the Lane verdict) runs the same two commands unchanged and its three verdicts — drift, header pointer, unexplained exclusion — are all still live; Lint & Repo Gates success and the dev's --ran agree. One marker is weakened, not holed: resolveTenantFieldName is now trivially present in the file that defines it; the other two markers still bind. The CLI inventory reads the artefact's rows, not its comment — unaffected. The sibling scripts/tenant-audit-census.mjs never modelled the mute by ledger or by namespace (no six-name hit; its PLATFORM_TYPES are TypeScript globals) — unaffected. A minor imprecision in the new docblock and $comment: both say non-null ⇒ in the audit and omit the not-federated term the code keeps; moot for the platform namespaces, none of which is external.

  6. Docblocks — RIGHT against ADR-0131 as shipped. approval-service.ts, business-unit-graph.ts, the B3 test comment and system-write-organization.ts now describe C1 (seed replay stamps), D9 (derive or refuse) and D13 (no per-table exemption), and stop citing sys_business_unit_member is unadjudicated in PLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 and the ledger. The claim that this takes chore: ADR-0131 small carriers — resolveDeclaredRuleScopes checks the raw page length, the semantic-entry and ceremony wording, and the help texts and docblocks that still say NULL organization means global #25203's item 4 is not verified by this record (chore: ADR-0131 small carriers — resolveDeclaredRuleScopes checks the raw page length, the semantic-entry and ceremony wording, and the help texts and docblocks that still say NULL organization means global #25203 is outside the brief's inputs); the rewrites stand on their own reading of the ADR.

  7. R1–R4 — cannot be affected by this diff, and the head's gates agree. No boot, ceremony, refusal, schema, DDL or migration path is touched. R1: Dogfood Regression Gate success; the dev's fresh boot answered 200. R2: 18.x upgrade harness (opt-in) success at 12:54:44Z on this head, plus the dev's 15/15 on both fixture legs. R3: the v17 refusal lives in boot code the diff does not reach. R4: the dev's walled os serve answered 200 and printed 43 WARN lines in the new text — the only R4-visible change, diagnostics only.

  8. Governance and shape. No governed path on the file list (Governed Surface Queue Guard success; check-governed-merges --branch NOT governed). Head repo equals base repo. Draft, no auto-merge armed.

② Semver level

RIGHT. .changeset/25201-platform-object-tenancy-ledger-retired.md grades @objectstack/objectql patch and @objectstack/driver-sql patch; Clause-②: no (narrowing) is identical on the PR body and in the changeset; Metadata-protocol: none; the bold BREAKING banner is present with the sentence that says why it ships at patch; and exactly one ADR-0087 marker is carried, an HTML comment reading adr-0087: not-required (runtime-export-retired @objectstack/objectql#PLATFORM_OBJECT_TENANCY, …#classifyPlatformObjectTenancy, …#isPlatformObjectOutOfTenantAuditScope, …#tenantScopedPlatformObjects, …#PlatformObjectTenancy, …#PlatformObjectTenancyEntry) with its why. That is the seventh category ADR-0087 added on 2026-10-12 for retired published runtime exports with no metadata surface — exactly this diff: no Zod key, no spec declaration and no stored shape moves, objectstack migrate meta has nothing to rewrite, and no tombstone is owed. The migration section gives six FROM → TO lines, one per removed name, pointing at resolveTenantFieldName (still exported) and the census. The level follows the repo rule as check-changeset-no-major.mjs encodes it: no (narrowing) beside none grades none-clean, every introduced entry patch, the BREAKING banner and the ADR-0087 marker carried. driver-sql's change is a log string behind an unchanged prefix — patch. Only the two packages whose published source moves are listed; plugin-approvals and plugin-sharing change comments only, so omitting them is right. Check Changeset success on all four runs.

③ Boundary flags

The dev's open_questions is empty. The dev's three out_of_scope_findings, restated in the PR body's Acceptance notes and Observation, each answered or escalated:

  1. Residue of PLATFORM_OBJECT_TENANCY outside CHANGELOGs — answered; two items escalated to the seat. The card's literal acceptance "git grep returns nothing outside CHANGELOGs" is not met and cannot be met by this PR: two hits are governed (.claude/skills/pm-dispatch/references/platform-readings.md:403, Tier S; docs/adr/0131 at lines 56, 878 and 1249, Tier H, where 878 is the D13 retirement list itself), two are dated records (docs/audits/2026-09-tenancy-ledger-namespace-regexp-census.md; scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs, which now throws at import as its own docblock prescribes). Live code and tests: zero hits. Escalated: (a) platform-readings.md:403 states a reading that is now false — 「租户普查 reach: in 只说有租户列;D9 系统写盖章只认 PLATFORM_OBJECT_TENANCY 的 tenant-scoped。」 — false since stage 8b and doubly so now; the seat cuts the Tier S edit; (b) the seat decides whether the dated audit script stays as a loud-failing record or is deleted. The ADR needs no amendment: D13 names the retirement and lines 56 and 1249 are dated anchors.

  2. Docs drift the dev found — escalated to chore: ADR-0131 small carriers — resolveDeclaredRuleScopes checks the raw page length, the semantic-entry and ceremony wording, and the help texts and docblocks that still say NULL organization means global #25203 (stale-text carriers). packages/spec/src/automation/schedule-organization.zod.ts:76 and its generated content/docs/references/automation/schedule-organization.mdx cite the deleted path for the sys_automation_run evidence; a packages/spec docblock edit regenerates the page and is not this card's surface. Added by this review, same carrier: content/docs/permissions/tenant-audit-census.mdx lines 49-50 say the engine mutes every isSystem write — stale since design: isSystem 写入是否在租户审计控制范围内?——#13178 类级装置(A/B/C)的共同前置,从未被裁过 #13491 and further from true now; it is hand-written prose outside check-tenant-audit-census's gated region. Also the comment at delivery-claim-tenant-audit.integration.test.ts:11-14 quotes the old tail while the test asserts only the unchanged prefix — cosmetic.

  3. Create-noise under a wall — answered, and escalated as a follow-up card. An elevated insert whose row carries organization_id reaches the driver with no tenantId and warns; pre-existing for application objects, now reaching 32 platform objects; diagnostics only, no write changes; the ruled direction (a warning, never a silent rewrite). The seat should file it in the 8c-1 design space: skip the insert audit when the row carries its tenant field, and let the engine declare the D8 wall-exemption to the driver so an exempt admin's write is not told to declare an option it cannot set. Not blocking.

  4. Claim-comment constraints — answered. No .github/workflows/** change is needed (lint.yml's step still checks something real, judgment ①5); package.json's two census scripts are untouched and still valid; no ORGANIZATION_OWNERSHIP_SHAPE_GENERATION or ceremony change is in the diff.

  5. Not verified by this record, named so the seat knows: the cloud repository's importers of the six names (outside the inputs; the changeset is the carrier), and the identity of chore: ADR-0131 small carriers — resolveDeclaredRuleScopes checks the raw page length, the semantic-entry and ceremony wording, and the help texts and docblocks that still say NULL organization means global #25203's item 4.

Implemented-by: claude/issue-25201-retire-platform-object-tenancy
Reviewed-by: session_01Y5583bs2RDd53MH6kM1ca3

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat ACCEPT · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-22T13:03Z. PR #25210 at aaa6ef6aa5ab46a82ba247dd58d6a2f92a304626 (#25201; claim 6339794203; os-dev-report 6341129117). ACCEPT.

Seat check

  • Shape: 14 files, +263 / −501. check-governed-merges --pr 25210: 0 of 14 paths governed, so this is an ordinary queue landing. .github/workflows/lint.yml is untouched, and no Tier H follow-up is owed.
  • What it does:
    • The PLATFORM_OBJECT_TENANCY ledger retires (ADR-0131 D13), along with six published @objectstack/objectql exports.
    • The engine's bypassTenantAudit isSystem mute is now decided by schema: a tenant column, not federated. A per-table list no longer decides it.
    • The [tenant-audit] warning keeps its prefix and stops telling a wall-exempt write to pass a tenantId.
    • The census tooling now points at the schema rule, and lint.yml's step still checks it.
    • No write path, D9 stamping or refusal, boot, ceremony or schema code moves.
  • Review: CONTRACT_REVIEW_TIER, narrowing face, from an isolated reviewer. PASS 6341425088 at this head.
    • The new mute is a strict subset of the old one: 32 platform objects with a tenant column lose the mute.
    • Its only behavioural reader is the SQL driver's warn early return (sql-driver.ts:15999; the seat re-checked it), so no write changes.
    • The changeset is right: patch, Clause-②: no (narrowing), Metadata-protocol: none, the BREAKING banner, and one ADR-0087 runtime-export-retired marker naming the six exports.
  • CI: the latest run of every check is success or skipped.
    • The opt-in 18.x upgrade harness lane ran on this head (the seat applied needs:upgrade-harness) and passed.
    • The Part-of check's one failure is the 11:52 run on the first body; its later runs pass.
  • R1–R4: no boot, ceremony or refusal code is touched.
    • Dogfood Regression Gate and the harness lane are green.
    • The dev's fresh boot and walled os serve both served 200, and full dogfood passed 2398/2398.
  • The review's escalations, and where each goes:
  • Landing: the seat flips the PR ready and enables auto-merge. Fixes #25201 closes the card.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 22, 2026 13:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 22, 2026 13:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 3a440b0 Oct 22, 2026
60 of 61 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25201-retire-platform-object-tenancy branch October 22, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants