Repository navigation
feat(spec,services): deployment-level state has no organization column — settings global rung, plumbing objects, the audit ledger, #12699 made total (ADR-0131 D7) #15207
Description
Activity
objectstack-fleet commented on Oct 6, 2026
v18 pre-opening re-verification (C6): mostly HOLDS. One listed object is wrong; four small refinements
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T14:38Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstack main 6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.
Holds:
- All eight listed objects still carry the injected organization column:
sys_job,sys_job_run,sys_job_queue;sys_flow_dispatch;sys_migration,sys_migration_journal;sys_secret;sys_presence.
- No PR since the cut touched their tenancy.
- Six of them have only system-context writers.
sys_presencehas no ObjectQL writer at all. sys_setting's global rung (settings-service.ts:1383,:2318-2336,:2436) holds.- §6 Q3 was accepted.
- feat(spec,security): OrgScopingEntitlement grows platform-global exemption + unbounded-admin suppression, consumed by Layer 0 arming #12699 holds. [finding] sys_activity declares environment_id as a live, indexed, non-deprecated column while its sys_metadata twin is marked deprecated — and no writer in this repo sets it #13433 was absorbed: it closed as a duplicate on 09-27.
Corrections:
sys_secretis tenant-attributed today, so take it off this card's list. The object secret-field producer writes it with the business write's driver options (objectql/src/engine.ts:8838-8851).SqlDriver.injectTenantOnInsertthen stampsorganization_id(driver-sql/src/sql-driver.ts:15551-15562), and the organization is bound into the encryption AAD (engine.ts:8826). Its fate belongs in C7's inventory (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211).- The one-sentence summary contradicts the scope:
sys_http_deliveryis excluded, and the notification inbox is deferred to C7. The claim states the scope as listed. - Name the existing attribution field:
sys_audit_log.tenant_id(sys-audit-log.object.ts:350,380), already written by four writers. - One new deployment-level audit writer: feat(security): record platform-admin standing on the audit ledger at boot #19194's
platform-admin-standing-audit.ts:197-238, which writes an owner-less row and cites D7.
Day one: dispatchable once #15193 closes, with the corrections above.
Generated by Claude Code
objectstack-fleet commented on Oct 7, 2026
Triage: unlocked, pm:blocked → pm:queue. The v18 line is open: #15193 was closed on the maintainer's word. C6, dispatchable first
Blocked-by: none
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T12:36Z. ⛔ Not a claim, ⛔ not a dispatch.
The blocker is released:
- [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193 closed
completedin this act (6037915987), on the maintainer's words in the triage seat's chat: 「我建议直接启动 v18 开发吧」, 「你应该先解锁 v18 所有的卡片」, 「同意」. - The ruling record is decision: open v18 now and ship it in stages — release the last 17.x from main first without waiting for #21908's deny (A), skip the last 17.x (B), or keep #22009's order (C)? #22050
6037890422(B: v18 develops onmain, with no last 17.x). - This card's
Blocked-by:named [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193 alone.
At claim (as #15193 requires, because these premises were measured in 2026-09): the file surface is re-verified against the then-current main. At this write, every repository path the body names in backticks exists on main (879bd38c5b).
- Order: C10 (cloud#1979) waits on this card, per its
Blocked-by:line.
The release state:
mainis not yet in Changesets pre mode; the opening card follows this unlock.- A breaking change landing before the opening is graded
minorwith its BREAKING banner and ADR-0087 disposition, under the launch-window convention. Once pre mode is in,majoris open. - ⛔ chore: version packages #21988 is not merged.
10 remaining items
objectstack-fleet commented on Oct 8, 2026
Claim: PM loop round 1 (stage: scope item (3), the settings global rung, of this card's three open items; item (1) landed as PR #22107, released 6045790111) · 2026-10-08T00:26Z
Session: session_01LAi5BVvQNiYzepSAcsoFLK
Account: os-litant (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-15207-platform-setting-global-rung
Worktree: objectstack-issue-15207
Domain: domain:spec (the card also carries domain:services; this seat holds it whole, as seat 2 did)
Seat: domain:spec#1 (seat post #6017)
File surface (at origin/main 51290bca2c; stop on breach and explain in the report). Scope item (3) alone: sys_setting's scope: 'global' rung leaves the tenant-scoped object (ADR-0131 D7, §6 Q3).
- The cascade and its writes:
packages/services/service-settings/src/settings-service.ts(the global rung's readers and writers: the rank table near:791,resolveKeyFromRowsnear:1384,get/getManysource handling near:2256,loadRowsnear:2328, the row-identity write near:2437, the global default near:646), withsettings-service.types.ts,settings-service-plugin.tsandsys-secret-orphan-report.tsin the same package where they read the global rung. - The new tenant-less object
sys_platform_setting: besidesys_settinginpackages/platform-objects/src/system/,systemFields.tenant: false, governed by object permission (D7).packages/platform-objects/src/system/sys-setting.object.ts(andsys-setting-audit.object.ts) only for theglobaloption, if no writer of it remains.packages/spec/src/system/constants/platform-object-names.tsfor the name;packages/spec/src/system/settings-manifest.zod.tsonly where a describe namessys_settingas the global rung's store. - ADR-0087: step-18 entries under
packages/spec/src/migrations/entries/semantic/18.*.tswith the regeneratedregistry.ts, if an option retires or a stored shape moves; the regenerated tenancy / system-context censuses. - Readers outside the family that read
sys_settingrows directly rather than through the service: the dev's census decides which read the global rung; candidates at this stamp arepackages/core/src/security/resolve-authz-context.ts(domain:engine),packages/cli/src/utils/secret-reference-union.tsandsys-secret-orphan-sweep.ts(domain:cli),packages/metadata-protocol/src/migrations/sys-setting-identity-index.ts(domain:engine). Any it must edit is declared by this seat before the PR leaves draft. - Tests of each package touched, and
.changeset/15207-*.md. Grade:minorwith its BREAKING banner and ADR-0087 dispositions while Changesets pre mode is not in (PR chore(release): enter Changesets pre mode (next) with onemajormarker, so v18 opens at 18.0.0-next.0 #22084 open),majoronce it is; the dev readsmainat PR time. - Not this stage: items (2)
sys_audit_logand (4) feat(spec,security): OrgScopingEntitlement grows platform-global exemption + unbounded-admin suppression, consumed by Layer 0 arming #12699 made total; the stored-row move of existing global rows (C7, feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211, which waits on this card). - Declared cross-lane files:
domain:services(service-settings) on [PM seat] domain:services · seat 2 — ⏳ vacant #21118 and [PM seat] domain:services — 🟢 os-bill #6021;domain:engine(platform-objects, andcore/metadata-protocolif the census needs them) on [PM seat] domain:engine — 🟢 os-litant · session_01EUBvqtauTDmHi2ZgY759p2 #6367 and [PM seat] domain:engine · seat 2 — ⏳ vacant #20966;domain:clion [PM seat] domain:cli — 🟢 os-warren · session_01RWZbGvPFcRKvUqASZtunCU #6024 if the census needs it.
Container & model:L,mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate"; the default tier for a judgment card). CeilingCONTRACT_REVIEW_TIERfor the review only: owed before enqueue (path limbpackages/spec/src/**, and this claim'sClause-②: yes), from an isolated at-tier subagent, because this seat is not served that tier.
Clause-②: yes (widening: a new platform objectsys_platform_setting, and a new name ifpackages/specexports one; the global rung's storage narrows onsys_setting. The dev measures the built declaration closure.)
Responsibility: n/a — not a defect card
Thread-read: 6045790111
Serial constraints cleared: - Of the 8 open PRs at this stamp, none touches
packages/services/service-settings/**,sys-setting.object.ts,platform-object-names.tsorsettings-manifest.zod.ts, and no liveClaim:names them. packages/spec/src/migrations/registry.ts(generated,merge=os-regen) is also written by PR feat(spec)!: refuse bare unique: true on a declared index at protocol 18 — stated scope, zero-drift conversion (ADR-0120 D2/D5a/D7) #22103 (ADR-0120 协议 18 项:D2 conversion(声明索引 unique: true → 'global')+ 裸 true 硬拒 + synonym pin 退役 —— 挂 18 列车,现在勿动工 #5082) and PR feat(spec)!: retire the flat string-list arm of manifest.permissions — the structured ADR-0025 block is the only form (#13458) #22094 (Phase 2 of #11333: retire the legacy string[] arm of manifest.permissions (major, standard retirement route) #13458), both seat 2: whichever lands later mergesmainthroughos-regen-merge.sh.- C7 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211,
pm:blocked) carries the stored-row move; C6(3) and C7 ship in the same release, which thetarget:v18board holds.
objectstack-fleet commented on Oct 8, 2026
os-dev-report
{
"issue": 15207,
"status": "done",
"branch": "claude/issue-15207-platform-setting-global-rung",
"pr": "#22166",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (subagent = the PM's id; resumed once after a container restart, same claim 6049595369, same branch and worktree)",
"premise_still_valid": true,
"summary": "Draft PR #22166 (Part of #15207, item (3) only) adds the tenant-less sys_platform_setting and moves the settings cascade's global rung onto it. The object has systemFields.tenant false, requiredPermissions manage_platform_settings, one row per (namespace, key) with unique 'global', sys_setting's value/encryption columns, and no scope/user_id/organization_id; settingsObjects registers it beside sys_setting. SettingsService writes a global-scope key there and reads the rung from there alone. Every sys_setting read now names its rungs ($or over tenant/user, or user_id/tenant), so a pre-v18 scope=global row is not a second source: no fallback read, no boot move. getMany reads the global rung once per call. The rank table, the lock check and source 'global' are unchanged. Also in the PR: (a) the CLI secret-reference union reads both settings holders, and an unreadable sys_platform_setting gaps the family (H3 closed; without it the orphan sweep deletes global-rung credentials); (b) a global-scope config_change audit row names sys_platform_setting via the new export CONFIG_CHANGE_GLOBAL_OBJECT_NAME; (c) sys_setting.scope retires 'global' (H6), while sys_setting_audit.scope keeps it; (d) one ADR-0087 D3 entry, sys-setting-global-rung-moved, plus a step-18 rationale fragment; (e) a minor + BREAKING changeset; (f) the tenancy census, the tenant-audit census and the erasure baseline are regenerated. No stored row moves (C7, #15211).",
"readings": {
"h1": "HOLDS. The built manifests have 109 global-rung keys in 7 namespaces: auth 29, ai 35, storage 11, mail 10, sms 10, knowledge 10, lifecycle 4. lifecycle.retention_overrides is the one tenant key. All 7 read and write under manage_platform_settings, i.e. live-edited in Setup. In-tree consumers re-read on change: plugin-auth and organizations (getNamespace plus subscribe), plugin-email, service-sms and service-storage (subscribe), lifecycle (on every sweep). ai and knowledge have no in-tree value reader beyond their runAction test handlers, which resolve live. No key is boot-read only, so nothing moves to configuration and no open_question is raised.",
"stop_condition_org_signal": "Did not fire. SettingsService.setMany is the only writer of a settings row. It writes under isSystem with no tenantId and a row that names no organization. sys_setting is unclassified, so resolveSystemInsertOrganization derives nothing. A pin writes a global key under ctx tenantId org_1, and the stored row has no organization_id. Two organization signals do sit on the global write path, but neither is row attribution: the CryptoContext tenantId (no in-tree provider binds it; decrypt passes none), and the config_change audit row's tenant_id (item 2's object).",
"h2": "Census of every non-test source naming sys_setting; the procedure fires on the union and on orphans.ts. Reads the global rung: cli/src/utils/secret-reference-union.ts (all rows, fixed here) and cli/src/commands/secret/orphans.ts lines 300-309 (the legacy-inline guard; OUTSIDE the surface, not edited). Do not read it: core/src/security/resolve-authz-context.ts:1696 (pins scope 'tenant'); metadata-protocol sys-setting-identity-index.ts (index maintenance only, no value read); mcp/src/plugin.ts and plugin-hono-server/src/current-user-endpoints.ts (comments only, they go through resolveLocalizationContext); cli sys-secret-orphan-sweep.ts and service-settings sys-secret-orphan-report.ts (pure, caller-supplied rows; the report has no in-tree caller).",
"h3": "TRUE, and closed in this PR. A handle held only in sys_platform_setting.value_enc is attributable and unreferenced by sys_setting, which is the deletable shape. Ablation B (the union reads sys_setting alone) turns three pins red: the union names the handle, an unreadable holder gaps the family, and the sweep decides referenced rather than deletable.",
"h4": "The LocalCryptoProvider v2 AAD is the 0xFF lead byte, a version label, then lp(scope) || lp(namespace) || lp(key), with scope 'settings'. No object name, no organization, no tenant. C7 copies value_enc verbatim and does not re-encrypt; the sys_secret row stays. A pin seals with tenantId org_1 in the context, holds the handle in a sys_platform_setting row, and resolves it. Recorded in the PR body.",
"h5": "Scratch harness, not committed: the real SecurityPlugin middleware, the registry-processed object and the shipped sets, posture none/single/isolated. organization_admin gets 403 PERMISSION_DENIED; admin_full_access is admitted with no filter; member gets 403; a system context is admitted. Control, the same object without requiredPermissions: organization_admin is admitted with NO filter on every posture. The settings door is unchanged: settings-admission-tenancy-posture and config-change-audit drive the plugin's own routes over a real ObjectQL with a global manifest, get 200, and the row lands in sys_platform_setting.",
"h6": "Half holds. No writer writes scope 'global' into sys_setting, so its option is retired with the ADR-0087 entry. sys_setting_audit.scope is still written with 'global' (buildSettingAuditWriter records entry.scope), so the mirror stays. The parity pin is re-premised: sys_setting.scope = SpecifierScopeSchema minus global; the audit object = SpecifierScopeSchema. SpecifierScopeSchema and source 'global' stay.",
"zone3_falsified": "The suggested pin 'a tenant and a user value still override it' contradicts the unchanged rank table. scopeRank puts global at rank 1 and the first non-null rung wins, so global OUTRANKS tenant and user, before and after this PR. The pin asserts the unchanged order instead: global from the new store outranks both, and tenant/user answer once it is empty. The 403 pin was measured by the scratch harness rather than committed, because a committed one would live in plugin-security, which is outside the surface (item (1)'s precedent).",
"c7_move": "For each sys_setting row at scope global: write sys_platform_setting with the same namespace/key, copying value, value_enc (verbatim), encrypted, locked, locked_reason and updated_by; remove the source. A pre-#8629 database can hold duplicate global rows for one (namespace, key); the new unique refuses the second, so the ceremony must pick one."
},
"tests": "Read at the merged head d047787. service-settings: full vitest, 37 files / 638 passed, including the new settings-global-rung.test.ts (8 cases, real ObjectQL). platform-objects: full vitest, 65 files / 1036 passed; the pre-fix run had 4 red in objects-es-es-echo-decisions.test.ts for the retired leaf, fixed. spec: vitest over src/system, src/migrations and api-methods-batch-conformance, 55 files / 1980 passed. cli: --project integration over the touched files (union, sweep, rewrap, src/commands/secret), 7 files / 91 passed. Read at the pre-merge head 1137107: cli --project unit, 263 files / 3874 passed; typecheck exit 0 for service-settings, platform-objects, spec and cli (the latter three include check:test-typecheck; service-settings' tsconfig includes src, tests included). Ablations, one-off, via scripts/ablation-replace.mjs in WRAP mode under the lock: each anchor 1 to 0 with a changed blob, each restore read blob == HEAD with an empty git diff HEAD, and git status read 0 lines afterwards. The subjects are imported by relative source path, so no dist is in the path. A (null-user read readmits global): 1 red. A2 (user read readmits global): 1 red. D (global rung read from sys_setting): 7 of 8 red. B (union reads sys_setting only): 3 red.",
"gates": "At d047787, dispatch-gates --commands --repo objectstack-ai/objectstack derived 125 families. All 125 ran, each exit code captured before any pipe, all 125 exit 0. --ran reconciles 125 derived / 125 run / 0 NOT-MEASURED / 0 UNRUN. Added vs the claim-time list: check-empty-changeset, check:engine-double-contract, check:objectql-double-limit, check:where-matcher, check:i18n-coverage, check:i18n-walk-parity, check:type-check-coverage, check:type-check-debt, check:yaml-examples, check:objectui-changeset, check:pm-changeset-deadline-census, release-rehearsal-clone and release-pending-publish self-tests. Red earlier and fixed: the census drift (two censuses), the objectql-double-limit double in the new test, and the query-options-erasure ratchet down (2 to 1). Earlier PREREQUISITE NOT MET on dual-build-cjs-loads and i18n-coverage was cured by building the missing packages; both are green. Lint, a proven narrowing at d047787: eslint --no-inline-config --format json over the 37 changed ts files gives 37 results, 0 errors, 0 warnings (none ignored); eslint.config.mjs enables no type-aware linting, so untouched files' verdicts cannot move; full pnpm lint is CI's. CI on #22166 at report time: 14 completed, 18 in_progress, 0 failed (in_progress, not waited on).",
"line_budget": "vs merge base 8fc50b7 (origin/main at the first merge; the second merge, ef1fcb2, made no change on this branch's side): 42 files, +1338 / -246 = 1584 changed lines, generated files included (translations, registry, censuses).",
"files_changed": [
".changeset/15207-settings-global-rung-platform-setting.md",
"content/docs/permissions/tenant-audit-census.mdx",
"docs/audits/2026-08-tenant-audit-write-call-sites.counts.md",
"packages/cli/src/commands/secret/rewrap.guards.test.ts",
"packages/cli/src/utils/secret-reference-union.test.ts",
"packages/cli/src/utils/secret-reference-union.ts",
"packages/cli/src/utils/sys-secret-orphan-sweep.test.ts",
"packages/cli/src/utils/sys-secret-rewrap.test.ts",
"packages/platform-objects/src/apps/translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts, es-ES.source-hashes.generated.ts, objects-es-es-echo-decisions.test.ts",
"packages/platform-objects/src/system/{index.ts, sys-platform-setting.object.ts, sys-platform-setting.object.test.ts, sys-setting.object.ts, sys-setting.organization-unique.test.ts, sys-setting.scope-options.test.ts}",
"packages/services/service-settings/src/{settings-service.ts, config-change-audit.ts, index.ts, manifest.ts}",
"packages/services/service-settings/src/ tests: config-change-audit, manifest, settings-admission-tenancy-posture, settings-crypto-fail-closed, settings-engine-bind-window, settings-getmany, settings-global-rung (new), settings-loadrows-scope, settings-prebind-read-warning, settings-secret-rotation, settings-system-context.pin, settings-user-reference.pin, sys-secret-orphan-report",
"packages/spec/src/migrations/entries/semantic/18.sys-setting-global-rung-moved.ts, packages/spec/src/migrations/registry.ts (regenerated + one rationale fragment)",
"packages/spec/src/system/constants/platform-object-names.ts",
"scripts/platform-object-tenancy-census.json, scripts/query-options-erasure-baseline.json"
],
"deviations": [
"Files outside the claim's surface, all needed and named here and in the PR body. (1) service-settings config-change-audit.ts, index.ts and manifest.ts, in the same package: manifest.ts registers the object; config-change-audit names the store a global change landed in. (2) The CLI test fixtures rewrap.guards.test.ts and sys-secret-rewrap.test.ts: the union now asks for the second holder. (3) platform-objects translation bundles plus the es-ES echo ledger test: generated fallout of the retired option. (4) content/docs/permissions/tenant-audit-census.mdx (3 hand-written counts the gate binds) and docs/audits/...counts.md: regenerated census. (5) scripts/query-options-erasure-baseline.json: a ratchet down.",
"cli/src/commands/secret/orphans.ts (domain:cli) is NOT edited. It reads the global rung for the sweep's legacy-inline guard; the PM declares it, and it is noted for C7.",
"Changeset arm is Clause-② yes (narrowing), not the claim's yes (widening ...): measured, the diff both widens (new object and exports) and narrows (option retired, rung storage moved). The PR body carries the claim's line verbatim at line start, then the measured arm.",
"Zone 3 pin 'tenant and user override the global value' replaced by the unchanged-rank pin (falsified; see readings.zone3_falsified). The 403 pin was measured by a scratch harness, not committed.",
"A stray file /build1.pid (one line, a PID) was written at the filesystem root by a mis-scoped shell variable before the container restart. Removal was refused by the safety check; it needs a person's rm.",
"The commit trailer pair is the AGENTS.md model-free form (Co-authored-by: Claude), not the harness reminder's model-named one; the PR footer is the AGENTS.md session-URL form."
],
"mcp_calls": "0",
"api_writes": "3 relay writes, each POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft) giving #22166, body read back byte-identical; (2) label-write --assign os-litant, i.e. the issues/22166 assignees write, read back as matching; (3) this os-dev-report comment, POST /repos//issues/15207/comments via post-stamped. Plus git pushes, which are not REST writes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: C7 #15211 · cli/src/commands/secret/orphans.ts lines 300-309 build the sweep's legacy-inline guard from sys_setting rows only; once C7 moves pre-Phase-3 global rows, inline ciphertext can sit in sys_platform_setting. Withhold-only guard; the union (which decides deletion) already reads both · noted, not filed · in PR Acceptance notes",
"carrier: C7 #15211 · metadata-protocol/src/migrations/sys-setting-identity-index.ts: two degraded-arm operator texts still say global-scope settings rows 'can still be created' in sys_setting; no writer creates one after this PR · noted, not filed · in PR Acceptance notes",
"carrier: 承接者:无 · settings-manifest.zod.ts module TSDoc names sys_setting as the store (and already omitted the global rung); no describe() names it, so it is untouched per the claim · noted, not filed · in PR Acceptance notes",
"carrier: 承接者:无 · SettingsService passes ctx.tenantId into CryptoContext on encrypt for every rung, while materialiseRow decrypts with none; no in-tree provider reads tenantId (zero pull) · noted, not filed · in PR Acceptance notes",
"carrier: 承接者:无 · reading only, NOT measured, and not in the PR body (the PR body is written once): the tenant/user rows SettingsService writes carry SETTINGS_SYSTEM_CONTEXT with no tenantId, and sys_setting is unclassified, so by reading the engine stamps no organization on a tenant row. A user-scoped key resolved with no userId reads every user's rows. Both pre-date this PR · dedupe words: sys_setting tenant row organization, settings system context tenantId, user-scope key without userId"
]
}
objectstack-fleet commented on Oct 8, 2026
Claim revision (file surface), and ✅ ACCEPT on the diff: PR #22166 at d0477879af. Landing held for the at-tier contract review and CI
domain:spec seat 1 (#6017) · os-litant · session session_01LAi5BVvQNiYzepSAcsoFLK · 2026-10-08T03:44Z · holder of claim 6049595369; the review of record for the report 6051681390.
Claim revision. It amends 6049595369 with the files the dev measured as needed, each named in the PR body:
service-settings:config-change-audit.ts,index.ts,manifest.ts(same package).domain:cli:packages/cli/src/utils/secret-reference-union.tsand its test, the H3 hazard closed;- the fixtures
rewrap.guards.test.ts,sys-secret-rewrap.test.tsandsys-secret-orphan-sweep.test.ts.
domain:engine:platform-objects' generated translation bundles and the es-ES echo-decisions test.- Generated / gate-bound:
content/docs/permissions/tenant-audit-census.mdx(three counts the census gate binds),docs/audits/2026-08-tenant-audit-write-call-sites.counts.md,scripts/platform-object-tenancy-census.json, andscripts/query-options-erasure-baseline.json(a ratchet down, 2 → 1). packages/cli/src/commands/secret/orphans.tsis read, not edited (see the out-of-scope list).
Checklist (read on GitHub and in the PR's own diff):
- Form: draft, base
main, assigneeos-litant. Line 1 isPart of #15207, and no closing word sits beside any card number in the body. Items (2) and (4) stay open. Line 2 isClause-②: yes, verbatim from the claim. The changeset saysyes (narrowing): the dev measured both arms, and both readings areyes. - Size and surface: 42 files, +1338 / −246 (1,584 lines including generated).
check-governed-merges: not governed. - The rulings, checked in
settings-service.ts:- A global-scope write lands in
sys_platform_setting. - The rung is read from that object alone (
loadGlobalRows). - The
sys_settingreads name their rungs in the query ($orovertenant/user), so a pre-v18scope = 'global'row is never a second source. ⛔ There is no fallback read and no boot move (D14). - The rank table, the lock check and
source: 'global'are unchanged. - One ADR-0087 D3 entry is registered:
sys-setting-global-rung-moved.
- A global-scope write lands in
- The object:
sys_platform_settinghassystemFields: { tenant: false }andrequiredPermissions: ['manage_platform_settings'](D7: object permission, not the wall), one row per(namespace, key), and noscope/user_id.sys_setting.scoperetiresglobal(H6).sys_setting_audit.scopekeeps it, because a global change is still audited there. - Readings accepted:
- H1: 109 global keys in 7 namespaces, all live-edited, so none moves to configuration.
- The stop condition: did not fire. No global write carries an organization.
- H3 is true, and closed in this PR: the union reads both holders, and an unreadable holder gaps the family. Ablation B turns 3 pins red.
- H4: the AAD binds scope,
namespaceandkeyonly. - H5:
organization_admingets 403 and the platform admin is admitted (a scratch harness, recorded in the body). The control without the capability admitsorganization_adminwith no filter.
⚠️ The seat corrects its own dispatch. Zone 3 suggested pinning "a tenant and a user value still override it". That contradicts the unchanged rank table:scopeRankputsglobalat rank 1 and the first non-null rung wins, so the global value outranks tenant and user, before and after this PR. The dev's pin asserts that unchanged order. The dispatch was wrong; the PR is right.
Changeset, read sentence by sentence against the diff:
- The headline and the "deployment-wide value" paragraph match.
- The
sys_platform_settingbullet (columns,tenant: false,requiredPermissions, writes only through the door) matches the object file. - The
SettingsServicebullet ("reads the cascade's global rung from there alone", "sys_settingreads excludescope = 'global'", order and lock unchanged,CONFIG_CHANGE_GLOBAL_OBJECT_NAME) matches the service diff. - The
sys_setting.scopebullet matches the option change and the audit mirror. - The
os secretbullet matches the union diff. - Each of "What moves for consumers" four bullets states what the code does. The "no re-encryption" sentence matches H4.
- The grade is
minor+ BREAKING + the ADR-0087 marker:.changeset/pre.jsonis absent onmain.
Out of scope (report):
orphans.ts:300–:309's legacy-inline guard and thesys-setting-identity-index.tsoperator texts → pointer6051723395on feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211 (C7), with the dev's move recipe.- The user-scoped key resolved with no
userId(read in source by the seat) → filed finding(service-settings): a user-scoped settings key resolved with no userId answers with whichever user row the namespace load returns first — measure who reaches it #22168, measure-reach first, under the "may leak data" exception. - The
settings-manifest.zod.tsmodule TSDoc, andCryptoContext.tenantIdon encrypt only → Acceptance notes (no reach, no pull).
Landing, held until:
- A
## Contract reviewPASS atCONTRACT_REVIEW_TIERis on record for the landing head. The path limb (packages/spec/src/**) and the claim'sClause-②: yesboth fire; it comes from an isolated at-tier reviewer. - Every check is green on that head (17 of 32 were
in_progressat this stamp). ⚠️ Release coupling: C6(3) and C7 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211) ship in the same release. Until C7's ceremony exists, an existing database's global settings answer from the next rung or the default. This is stated in the changeset and on feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211, and goes to the maintainer in the round report.
objectstack-fleet commented on Oct 8, 2026
os-dev-report
{
"issue": 15207,
"status": "done",
"branch": "claude/issue-15207-platform-setting-global-rung",
"pr": "#22166",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (subagent = the PM's id); landing round on claim 6049595369, revised 6051733303",
"premise_still_valid": true,
"summary": "A PURE regeneration hop. The new head is 92f3370, fast-forward pushed from d047787 (d047787..92f3370). Its single merge commit has parents d047787 and 7ef50a4 (origin/main), and it was made by scripts/pm/os-regen-merge.sh, which reported no conflict, kept the branch's bytes of scripts/platform-object-tenancy-census.json (main did not touch it), and committed the merge before any regeneration. On the committed merge, the full regeneration chain (gen:migration-registry, gen:spec-changes, gen:upgrade-guide, then check:generated against a freshly built spec dist) wrote ZERO bytes: git status clean, so there is no separate regeneration commit. The merged registry already equals the generator's output: 396 semantic entries, including main's manifest-permissions-string-list-retired and flow-script-subflow-config-undeclared-keys-refused, beside this PR's sys-setting-global-rung-moved. No file was hand-edited, and the PR body is untouched. The PR's net diff against main is still 42 files, +1338 / -246.",
"regeneration": {
"tool": "bash scripts/pm/os-regen-merge.sh: step 1 git merge origin/main (registry.ts auto-merged, no conflict); step 2 no os-regen path to take from main beyond the merge result (the branch-only census kept); step 3 merge committed first; step 4 run by hand as below",
"generators_run_on_92f33709c8": "pnpm --filter @objectstack/spec gen:migration-registry (wrote registry.ts, 396 semantic / 247 retired-key / 218 retired-def, byte-identical), gen:spec-changes, gen:upgrade-guide: git status --porcelain empty afterwards",
"regeneration_commit": "none: the generators changed nothing, so there was nothing to commit"
},
"rationale_order": "Reported, not moved. main's flow-script-subflow-config-undeclared-keys-refused (from #22129) also takes order 87, the same number as this PR's sys-setting-global-rung-moved. That is a TIE, not a split: the list documents that two retirements in flight may take the same number and then render in id order, so main's fragment renders first and this PR's renders immediately after it, as the last two fragments of step 18. The list stays sorted by id (99 fragments). Ties already exist at 56, 60, 62, 66, 67, 74, 77, 85 and 86. The seat decides whether to renumber; this round leaves it as is.",
"tests": "At HEAD 92f3370: pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2 scripts/step18-rationale-merge.test.ts scripts/conversions-major18-merge.test.ts gives 2 files / 21 tests passed (lock VERDICT command-exit 0). The spec dist was rebuilt on this head (pnpm --filter @objectstack/spec build, lock VERDICT command-exit 0) before check:generated.",
"gates": "All at HEAD 92f3370, each exit captured before any pipe: pnpm --filter @objectstack/spec check:generated exit 0 ('All 15 generated artifacts are up to date', against the dist built on this head; declaration stamp match); pnpm --filter @objectstack/spec check:migration-registry exit 0 ('src/migrations/registry.ts is current (396 semantic, 247 retired-key, 218 retired-def)'); node scripts/check-platform-object-tenancy-census.mjs exit 0 (84 objects, 50 in reach, 34 outside); its --self-test exit 0; node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset, registered sys-setting-global-rung-moved); node scripts/check-tenant-audit-census.mjs exit 0 (233 sites, 23 prose figures held). CI on the new head is not waited on.",
"purity": {
"command": "comm -23 <(git diff --name-only d047787 92f3370 | sort) <(git diff --name-only ef1fcb2 7ef50a4 | sort) | grep -v -x -F -e packages/spec/src/migrations/registry.ts -e packages/spec/spec-changes.json -e docs/protocol-upgrade-guide.md -e scripts/platform-object-tenancy-census.json",
"output": "(empty)",
"counts": "files differing d047787 vs 92f3370: 60; files main changed ef1fcb2..7ef50a4: 60; the set difference BEFORE subtracting the regenerated outputs is already 0 lines; after subtracting: 0 lines. ef1fcb2 is an ancestor of 7ef50a4 (merge-base --is-ancestor exit 0).",
"branch_side": "Of the 42 files in the PR's net diff (git diff 7ef50a4 92f3370), 41 have the same blob at d047787 and 92f3370. The one that differs is packages/spec/src/migrations/registry.ts, which now also carries main's two new entries. The net diff is +1338 / -246, unchanged from d047787."
},
"mcp_calls": "0",
"api_writes": "1 relay write: this os-dev-report comment, POST /repos/objectstack-ai/objectstack/dispatches (fleet-write.yml as objectstack-fleet[bot]) executing POST /repos//issues/15207/comments via post-stamped. Plus 1 git push (fast-forward d047787..92f3370), which is not a REST write. The PR body and labels are untouched.",
"deviations": [
"No regeneration commit exists: the generators wrote nothing on the committed merge, so the hop is one merge commit only.",
"The worktree was recreated with git worktree add on the existing branch, then pnpm install (no tracked file changed)."
],
"files_changed": [],
"line_budget": "unchanged: vs origin/main 7ef50a4, 42 files, +1338 / -246",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commented on Oct 8, 2026
os-dev-report
{
"issue": 15207,
"status": "done",
"branch": "claude/issue-15207-platform-setting-global-rung",
"pr": "#22166",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (subagent = the PM's id); landing round 2 on claim 6049595369, revised 6051733303",
"premise_still_valid": true,
"summary": "Regeneration hop with ONE hand edit, reported below. The new head is c3f5925, fast-forward pushed from 92f3370 (92f3370..c3f5925); the merged main tip is 6ed0c0f. It is two commits. 61edbce is the merge of origin/main 6ed0c0f made by scripts/pm/os-regen-merge.sh: it stopped on the two census files, which were brought to a committable state by taking main's side of each conflict hunk, and the script rerun skipped step 1, redid step 2 and left nothing to commit. c3f5925 is the regeneration: node scripts/tenant-audit-census.mjs --write on the merged tree, plus the one prose figure the gate still refused. The four translation bundles main also touched text-merged clean, and the PR's delta in each is byte-identical to before. The other regenerators (platform-object-tenancy-census --write, gen:migration-registry, gen:spec-changes, gen:upgrade-guide) wrote nothing. The PR's net diff against main is still 42 files, +1338 / -246.",
"hand_edit": {
"file": "content/docs/permissions/tenant-audit-census.mdx",
"line": 125,
"before": "forwarding shim cannot, and 54 of the 232 sites are spelled that way. A",
"after": "forwarding shim cannot, and 52 of the 232 sites are spelled that way. A",
"why": "The line is hand-written prose OUTSIDE the generated region, so --write does not touch it. It conflicted: this branch had '52 of the 233', main had '54 of the 232'. After taking main's side and running --write, check-tenant-audit-census refused exactly this figure: '[prose-count] the page states 54 for "sites whose options argument is unreadable (limits section)"; the census says 52'. The edit sets it to the census's value. 232 is main's total, and 52 reflects this PR's two settings-service sites that moved from unreadable to readable, the same -2 the PR carried at 92f3370. No other prose figure was refused: '60 sites' / '52 were simply unread' at lines 153-154 text-merged clean from this branch, and the gate holds all 23 prose figures."
},
"merge_resolution": "Both census files were brought to a committable state by taking main's side of each conflict hunk, as the os-regen-merge refusal prescribes for generated content, then rewritten by the census tool. The counts file is rendered whole by renderCountsFile, so the side taken does not survive --write. In the page, the three conflict hunks were two generated-region rows plus the line-125 prose above. Hand-written lines outside those hunks were text-merged by git, both sides' intents kept. The staged regeneration diff was inspected with git diff --cached before committing.",
"tests": "At HEAD c3f5925, after building the service-settings closure (lock VERDICT command-exit 0): pnpm --filter @objectstack/service-settings exec vitest run, exit 0, 38 files / 642 passed (main added one file); pnpm --filter @objectstack/platform-objects exec vitest run, exit 0, 65 files / 1036 passed. Each exit was captured per part.",
"gates": "All at HEAD c3f5925, each exit captured before any pipe: node scripts/check-tenant-audit-census.mjs exit 0 (232 write call sites certified, 23 prose figures held); node scripts/check-platform-object-tenancy-census.mjs exit 0 (84 objects, 50 in reach, 34 outside); pnpm --filter @objectstack/spec check:generated exit 0 ('All 15 generated artifacts are up to date', against a spec dist built on this head); pnpm --filter @objectstack/spec check:migration-registry exit 0 (396 semantic, 247 retired-key, 218 retired-def); node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (registered sys-setting-global-rung-moved). Extra, because main's commits touched the PR's four translation bundles: node scripts/check-i18n-bundles.mjs --filter=platform-objects exit 0 ('in sync (11 bundle(s))', with the CLI closure built on this head). CI on the new head is not waited on.",
"purity": {
"command": "comm -23 <(git diff --name-only 92f3370 c3f5925 | sort) <(git diff --name-only 7ef50a4 6ed0c0f | sort)",
"output": "(empty, 0 lines), BEFORE subtracting any regenerated output",
"counts": "92f33709c8 vs c3f5925: 74 files; main 7ef50a4..6ed0c0f: 74 files; 7ef50a4 is an ancestor of 6ed0c0f (exit 0); 92f3370 is an ancestor of c3f5925 (fast-forward).",
"branch_side": "Of the 42 files in the PR's net diff (git diff 6ed0c0f c3f5925), 36 have the same blob at 92f3370 and c3f5925. The 6 that differ are all files main also changed: (1) the four translation bundles, en / es-ES / ja-JP / zh-CN .objects.generated.ts, where the branch's own -U0 delta against main is byte-identical before and after (md5 of the delta lines equal per file); (2) the two census files, regenerated, plus the line-125 hand edit above. Net: +1338 / -246, unchanged."
},
"rationale_order": "Unchanged since round 1. main brought no new step-18 rationale fragment this round (396 semantic entries, as before). The order-87 tie with flow-script-subflow-config-undeclared-keys-refused stands as reported.",
"mcp_calls": "0",
"api_writes": "1 relay write: this os-dev-report comment, POST /repos/objectstack-ai/objectstack/dispatches (fleet-write.yml as objectstack-fleet[bot]) executing POST /repos//issues/15207/comments via post-stamped. Plus 1 git push (fast-forward 92f3370..c3f5925), which is not a REST write. The PR body and labels are untouched.",
"deviations": [
"One hand edit beyond the regenerators: tenant-audit-census.mdx line 125, the figure 54 to 52, to the census's value (see hand_edit). Reported for the seat's judgment on whether the PASS carries forward.",
"The merge needed a resolution commit (61edbce), as the os-regen-merge script prescribes when it stops on a not-driver-managed conflict; the regeneration is its own commit (c3f5925)."
],
"files_changed": [
"content/docs/permissions/tenant-audit-census.mdx (regenerated region + the line-125 figure)",
"docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (regenerated)"
],
"line_budget": "unchanged: vs origin/main 6ed0c0f, 42 files, +1338 / -246",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commented on Oct 8, 2026
os-dev-report
{
"issue": 15207,
"status": "done",
"branch": "claude/issue-15207-platform-setting-global-rung",
"pr": "#22166",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (subagent = the PM's id); landing round 3 on claim 6049595369, revised 6051733303",
"premise_still_valid": true,
"summary": "Census conflict sync, the same shape as round 2. The new head is 2d4170f, fast-forward pushed from c3f5925 (c3f5925..2d4170f); the merged main tip is 7d7943d. It is two commits. 0328884 is the merge of origin/main 7d7943d made by scripts/pm/os-regen-merge.sh: it stopped on the same two census files, which were brought to a committable state by taking main's side of each conflict hunk, and the script rerun skipped step 1, redid step 2 and left nothing to commit. 2d4170f is the regeneration: node scripts/tenant-audit-census.mjs --write on the merged tree, plus the one prose figure the gate still refused, restating the PR's -2 on main's new total. The other regenerators (platform-object-tenancy-census --write, gen:migration-registry, gen:spec-changes, gen:upgrade-guide) wrote nothing. git diff --shortstat 7d7943d 2d4170f: 42 files changed, 1338 insertions(+), 246 deletions(-), unchanged.",
"hand_edit": {
"file": "content/docs/permissions/tenant-audit-census.mdx",
"line": 125,
"before": "forwarding shim cannot, and 54 of the 234 sites are spelled that way. A",
"after": "forwarding shim cannot, and 52 of the 234 sites are spelled that way. A",
"why": "Hand-written prose OUTSIDE the generated region, which --write does not touch. It conflicted: this branch had '52 of the 232', main had '54 of the 234'. After taking main's side and running --write, check-tenant-audit-census refused exactly this figure: '[prose-count] the page states 54 for "sites whose options argument is unreadable (limits section)"; the census says 52'. The edit sets it to the census's value: main's new total 234, with the PR's -2 unreadable restated, the same shape as round 2. No other prose figure was refused; lines 153-154 ('60 sites' / '52 were simply unread') text-merged clean, and all 23 prose figures are held."
},
"tests": "At HEAD 2d4170f, after building the service-settings closure (lock VERDICT command-exit 0): pnpm --filter @objectstack/service-settings exec vitest run, exit 0, 38 files / 642 passed; pnpm --filter @objectstack/platform-objects exec vitest run, exit 0, 65 files / 1036 passed. Each exit was captured per part.",
"gates": "All at HEAD 2d4170f, each exit captured before any pipe: node scripts/check-tenant-audit-census.mjs exit 0 (234 write call sites certified, 23 prose figures held); node scripts/check-platform-object-tenancy-census.mjs exit 0 (84 objects, 50 in reach, 34 outside); pnpm --filter @objectstack/spec check:generated exit 0 ('All 15 generated artifacts are up to date', against the spec dist built on this head); pnpm --filter @objectstack/spec check:migration-registry exit 0 (396 semantic, 247 retired-key, 218 retired-def); node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (registered sys-setting-global-rung-moved). CI on the new head is not waited on.",
"purity": {
"command": "comm -23 <(git diff --name-only c3f5925 2d4170f | sort) <(git diff --name-only 6ed0c0f 7d7943d | sort)",
"output": "(empty, 0 lines), BEFORE subtracting any regenerated output",
"counts": "c3f59255c5 vs 2d4170f: 44 files; main 6ed0c0f..7d7943d: 44 files; 6ed0c0f is an ancestor of 7d7943d (exit 0); c3f5925 is an ancestor of 2d4170f (fast-forward, exit 0).",
"shortstat": "git diff --shortstat 7d7943d 2d4170f: 42 files changed, 1338 insertions(+), 246 deletions(-)",
"branch_side": "Of the 42 files in the PR's net diff, 40 have the same blob at c3f5925 and 2d4170f. The 2 that differ are the census page and the counts file: regenerated, plus the line-125 hand edit above."
},
"rationale_order": "Unchanged. main brought no new step-18 entry (396 semantic entries); the order-87 tie stands as reported in round 1.",
"mcp_calls": "0",
"api_writes": "1 relay write: this os-dev-report comment, POST /repos/objectstack-ai/objectstack/dispatches (fleet-write.yml as objectstack-fleet[bot]) executing POST /repos//issues/15207/comments via post-stamped. Plus 1 git push (fast-forward c3f5925..2d4170f), which is not a REST write. The PR body and labels are untouched.",
"deviations": [
"One hand edit beyond the regenerators: tenant-audit-census.mdx line 125, the figure 54 to 52, to the census's value (see hand_edit), the same shape as round 2.",
"The merge needed a resolution commit (0328884), as the os-regen-merge script prescribes when it stops on a not-driver-managed conflict; the regeneration is its own commit (2d4170f)."
],
"files_changed": [
"content/docs/permissions/tenant-audit-census.mdx (regenerated region + the line-125 figure)",
"docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (regenerated)"
],
"line_budget": "unchanged: vs origin/main 7d7943d, 42 files, +1338 / -246",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commented on Oct 8, 2026
Landed: scope item (3), PR #22166 → c52bfb417b. The card stays open: pm:dispatched → pm:queue for items (2) and (4)
domain:spec seat 1 (#6017) · os-litant · session session_01LAi5BVvQNiYzepSAcsoFLK · 2026-10-08T07:55Z · holder of claim 6049595369 (revised 6051733303), which this act releases.
- Landed: PR feat(service-settings,platform-objects)!: the settings cascade's global rung moves to the tenant-less sys_platform_setting (ADR-0131 D7) #22166 merged through the merge queue at 2026-10-08T07:54Z as
c52bfb417b. It has one parent (7b926f7600) and is an ancestor oforigin/main. - Content check: all 42 files of the PR's net diff on
c52bfb417bare blob-equal to the accepted head2d4170f619. - Review of record:
- ACCEPT on the diff
6051733303(atd0477879af), and the at-tier contract review PASS6051891742. - The PASS was carried to the landed head over three re-sync hops, each with a
Regen-provenance:pointer:6052193727,6053184036and6053878996. - The second and third hops each carried one gate-bound census figure, which the seat read and recorded.
- The PR was dequeued once, at 2026-10-08T06:16Z, on a
MERGE_CONFLICTin the two census files, and re-entered after the third hop.
- ACCEPT on the diff
- What now holds:
- A global-scope setting lives in the tenant-less
sys_platform_setting(systemFields: { tenant: false },requiredPermissions: ['manage_platform_settings'], ADR-0131 D7). - The cascade's global rung reads only that object.
- The
sys_settingreads name their rungs in the query (tenant,user), so a pre-v18scope = 'global'row is never a second source. ⛔ There is no fallback read and no boot move (D14). sys_setting.scoperetiresglobal, and the ADR-0087 D3 entrysys-setting-global-rung-movedis registered.- It ships
minorwith its BREAKING banner.
- A global-scope setting lives in the tenant-less
⚠️ Release coupling, unchanged:- C6(3) and C7 ship in the same release.
- feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211's ceremony moves the existing
scope = 'global'rows intosys_platform_setting. - The move recipe is on feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211 (
6051723395), and no re-encryption is needed: the AAD binds scope, namespace and key. - Until C7 lands, a deployment upgraded with existing global rows reads them as absent.
Release: session_01LAi5BVvQNiYzepSAcsoFLK · why: a partial landing (Part of #15207); item (3) is done, and items (2) and (4) are separate claims · to: pm:queue, unassigned, for items (2) and (4).
Items (2) and (4), for the next claim. These are unchanged from the item (1) landing record 6045790111, which also holds the seat lean A on each:
- (2)
sys_audit_log: the attribution field, plus the platform tenant RLS policy. - (4) feat(spec,security): OrgScopingEntitlement grows platform-global exemption + unbounded-admin suppression, consumed by Layer 0 arming #12699 made total: a claim of its own on
domain:engine+plugin-security.
Out of scope, filed: #22168. A user-scoped settings key resolved without a userId reads the first user row. It pre-dates this PR, and its first step is to measure reach.
objectstack-fleet commented on Oct 8, 2026
Claim: PM loop round 1 (stage: scope item (2), sys_audit_log, of this card's two open items; item (1) landed as PR #22107, item (3) as PR #22166 c52bfb417b, released 6055410828) · 2026-10-08T08:20Z
Session: session_01LAi5BVvQNiYzepSAcsoFLK
Account: os-litant (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-15207-audit-log-attribution
Worktree: objectstack-issue-15207-audit
Domain: domain:spec (the card also carries domain:services; this seat holds it whole, as for item (3))
Seat: domain:spec#1 (seat post #6017)
File surface (at origin/main 0e9371f0c6; stop on breach and explain in the report). Scope item (2) alone, under ADR-0131 D7: sys_audit_log carries no injected organization column; the organization a row is about is the plain attribution field tenant_id, which the tenant-field resolver does not claim; the object is governed by object permission, not by the wall.
- The object:
packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts(systemFields.tenant: false;tenant_idstays the attribution lookup), and the plugin-audit writers that stamp it, if any rely on the injected column. - The read scope (option A of report
6042515710, the seat lean recorded in6043540291): inpackages/plugins/plugin-security/src/objects/default-permission-sets.ts, a platform tenant RLS policy onsys_audit_log(tenant_idequal to the caller's organization), stripped undersingleby the existing provenance rule (ADR-0105 D3), plus an explicitsys_audit_logentry inorganization_adminwithoutviewAllRecords/modifyAllRecords. Platform admins keep reading everything (view_all_audit_log). - Retention:
packages/objectql/src/lifecycle/lifecycle-service.ts, so that per-tenant audit retention partitions on the attribution field rather than the retired column. - The description:
view_all_audit_loginpackages/spec/src/security/capabilities.ts(and its mirror ineval-user.zod.tsif it restates it), re-premised on the attribution field. - ADR-0087: step-18 entries and the regenerated
registry.ts, if a stored shape moves; the regenerated tenancy censuses. Tests of each package touched, and.changeset/15207-*.md(minorwith its BREAKING banner). - Not this stage: item (4), feat(spec,security): OrgScopingEntitlement grows platform-global exemption + unbounded-admin suppression, consumed by Layer 0 arming #12699 made total. The stored-row move (existing rows' injected column dropped, after its values are confirmed in
tenant_id) belongs to C7's inventory (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211, fate 1, the ADR-0120 D4 ceremony). The PR body names the fate for C7, and C6(2) ships in C7's release, as C6(3) does. - Declared cross-lane files:
domain:services(plugin-audit,plugin-security) on [PM seat] domain:services · seat 2 — ⏳ vacant #21118 and [PM seat] domain:services — 🟢 os-bill #6021;domain:engine(objectql) on [PM seat] domain:engine — 🟢 os-litant · session_01EUBvqtauTDmHi2ZgY759p2 #6367 and [PM seat] domain:engine · seat 2 — ⏳ vacant #20966.
Container & model:L,mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate"). An at-tier contract review is owed before enqueue: the path limb ispackages/spec/src/**, and this claim'sClause-②: yes. It comes from an isolated at-tier subagent.
Clause-②: yes (narrowing:sys_audit_logloses its injected organization column, and its read scope moves to a declared row policy; the dev measures the built declaration closure)
Responsibility: n/a, not a defect card (ADR-0131 C6, item (2))
Thread-read: 6055410828
Premises (verify each before code, with a reading; ⛔ stop and report a fork if one fails, and ⛔ do not fall back to option B (a read middleware) or option C (tenant_idas the tenancy anchor)): - P1.
sys_audit_logholds rows about deployment-level actions that carry no organization:config_change,import,platform_admin_standing_change. - P2. With the injected column gone,
organization_admin's'*'bypass skips Layer 1, so without an explicit entry an organization admin would read every organization's rows. - P3. The ADR-0105 D3 provenance rule strips a platform tenant RLS policy under
single.
Serial constraints cleared: - Of the 21 open PRs at this stamp, none touches
sys-audit-log.object.ts,plugin-audit/src,default-permission-sets.ts,lifecycle-service.tsorsecurity/capabilities.ts. - Item (4) and C7 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211,
pm:blocked,domain:engine) are not in flight.
⛔ BLOCKED — the v18 development line is not open.
Blocked-by: #15193
History: this line read
Blocked-by: #15193until 2026-09-27, inside backticks, so no unlock scan could read it (the triage census counted it as a blocked card with no machine target). The same targets now sit one per line, undecorated; nothing else changed (triage seat, session_01W89enF2dYV7K4N2Fbfj33f).Part of #15194 (ADR-0131 execution tree). ⛔ Do not claim, assign or dispatch this card while #15193 is open, whatever its other labels say.
In one sentence. Tables that belong to no organization — the job queue, delivery records, migration journals, the audit ledger, the notification inbox — lose their organization column; deployment-level runtime settings leave
sys_setting; and cloud's "this deployment declares this object platform-global" switch becomes simply "on this deployment the object has no organization column".Scope. (1)
systemFields.tenant: falseon the objects no writer attributes to an organization:sys_job,sys_job_run,sys_job_queue,sys_flow_dispatch,sys_migration,sys_migration_journal,sys_secret(scoped through its owning setting),sys_presence— each confirmed by a writer census with a firing control, not by the name looking infrastructural. ⛔ NOTsys_http_delivery(#13565 stamps it from the webhook's organization;redeliver()walls by tenant) and ⛔ NOTsys_email(#11741 / #11303 decision 2 stamp it at the producers) — both are tenant data. Thesys_inbox_message/sys_notification*/sys_user_preferencefamily is decided by its writer facts (recipient-anchored in cloud's reading) and recorded in the C7 inventory. ADR-0087 entry per removed column. (2)sys_audit_log: no injected organization column; the organization a row is about becomes a plain attribution field under a name the tenant-field resolver does not claim (notorganization_id); RLS readers of the audit page filter on it explicitly. (3)sys_settingscope: 'global'rows leave the tenant-scoped object per §6 Q3 — configuration, or a tenant-lesssys_platform_setting;settings-service.ts's user → tenant → deployment cascade reads the new source. (4) #12699:platformGlobalObjectsbecomes an input toresolveInjectedSystemColumnson the declaring deployment — no column injected, so Layer 0 and the driver have nothing to scope; the stand-down semantics retire.Absorbs: #13433 (
sys_activity.environment_iddeclared live with no writer) belongs to this census — read it and give that column a verdict in the same pass.Acceptance. DDL for each listed object carries no
organization_id; the settings cascade resolves deployment values from the new source (pinned); an audit row about a deployment-level action is written without refusal and is visible to platform admins; on a deployment declaring an object platform-global the table has no column (cloud pins this in C10).Refs: ADR-0131 D7 · ADR-0007 · ADR-0057 · #12699 · #13565 · #11741 · #13636 (
sys_audit_logspecimen) · #13433 · #13564 read-side ledger U-A.