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
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 4, 2026 - added a commit that references this issue
on Sep 6, 2026 - added a commit that references this issue
on Sep 9, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorMore actionsv18 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 objectstackmain6befe19c6e. 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
- All eight listed objects still carry the injected organization column:
objectstack-fleet commented
on Oct 7, 2026 ContributorMore actionsTriage: unlocked,
pm:blocked→pm:queue. The v18 line is open: #15193 was closed on the maintainer's word. C6, dispatchable firstBlocked-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 onmain(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.
- [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193 closed
34 remaining items
objectstack-fleet commented
on Oct 8, 2026 ContributorMore actionsClaim: PM loop round 1 (stage: scope item (4), #12699 made total, the last open item of this card; items (1), (2) and (3) landed as PRs #22107, #22266 and #22166, released
6061099316) · 2026-10-08T14:19Z
Session:session_01LAi5BVvQNiYzepSAcsoFLK
Account:os-litant(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-15207-platform-global-no-column
Worktree:objectstack-issue-15207-global
Domain:domain:spec(the card also carriesdomain:services; this seat holds it whole, as for items (2) and (3))
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main79c35d45e2; stop on breach and explain in the report). Scope item (4) alone, under ADR-0131 D7: "an object a deployment declares platform-global gets no organization column on that deployment (the injected-columns plan reads the declaration), so Layer 0 and the driver agree by having nothing to scope". ADR-0131 retires #12699's stand-down semantics.- The plan:
packages/spec/src/data/injected-system-columns.ts(resolveInjectedSystemColumnstakes the deployment'splatformGlobalObjectsas an input), withsecurity/tenancy-posture.tsandsecurity/tenant-layer0-verdict.tswhere they describe the carve-out. - The reader:
packages/objectql/src/registry.ts(where the plan is applied) andengine.tswhere it narrates the carve-out. - The stand-down retires:
packages/plugins/plugin-security/src/security-plugin.ts(thegetObjectSecurityMetafold and the boot log), anddeployment-org-scoping-entitlement.tsif its reader moves. - The ordering: the org-scoping service is registered by the enterprise runtime and may arrive after object registration. Its order is declared through ADR-0116's mechanisms (
packages/core/src/plugin-order.ts, the provider and requirer declarations), not assumed. - ADR-0087: step-18 entries and the regenerated
registry.tsif a stored or authored shape moves; the regenerated tenancy censuses. Tests of each package touched, and.changeset/15207-*.md(majorunder pre mode if breaking, with its banner). - Not this stage: existing rows' column on a declaring deployment. That is 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) and cloud's C10 backfill. The PR body names the fate for C7.
- Declared cross-lane files:
domain:engine(objectql,core) on [PM seat] domain:engine — ⏳ vacant #6367 and [PM seat] domain:engine · seat 2 — 🟢 os-tesla · session_01Bw3y2DWhT9RPnrmDsNqEVG #20966;domain:services(plugin-security) on [PM seat] domain:services · seat 2 — 🟢 os-elon-musk #21118 and [PM seat] domain:services — ⏳ vacant #6021.
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: on a deployment that declares an object platform-global, that object carries no organization column; the dev measures the built declaration closure)
Responsibility: n/a, not a defect card (ADR-0131 C6, item (4))
Thread-read: 6061099316
Premises (verify each before code, with a reading; ⛔ stop and report a fork if one fails, and ⛔ keep no fallback: no dual read and no stand-down fold beside the no-column plan, per ADR-0131 D14): - P1. Today a declared platform-global object still gets the injected organization column, and plugin-security stands Layer 0 down for it (
getObjectSecurityMeta). - P2. The declaration can reach the plan before the object's columns are fixed, through an ADR-0116 declared order and with no boot move outside it.
- P3. With the key absent, every object's plan is byte-identical to today (fail closed). With junk, no object is exempted, as the entitlement reader already rules.
- P4. The only declarer is cloud's control plane (ADR-0131 C10), so this repo's pins use a fixture provider.
Serial constraints cleared: - Of the open PRs at this stamp, four touch files on this surface. Whichever lands later merges
main:- feat(plugin-security)!: refuse a principal-less, non-system data-engine context (ADR-0096 D5 strict mode) #22297 (security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908):
security-plugin.ts; - fix(spec)!: defineSeed refuses a record key the target object does not have #22294 (spec:
defineSeedaccepts a misspelled record key at compile time andos validatepasses it, although its JSDoc promises "typos in record field names are caught at compile time" #22149):injected-system-columns.ts; - feat(objectql)!: positions, permission sets and capabilities hold one name per deployment — a second holder is refused at registration, naming both #22197 (feat(objectql,metadata,runtime)!: refuse a package whose position, permission set or capability name is already held by an installed package, the environment catalog or a built-in (ruling Q4 = A on #15196; narrows ADR-0048 §3.4) #22135):
objectql/src/registry.ts; - feat(plugin-auth,objectql,metadata-protocol,runtime)!: under
singlethe Default Organization exists before the seeds and the listener; an unowned seed row or system write is derived there or refused (ADR-0131 C1) #22186 (feat(objectql,plugin-auth): the Default Organization is load-bearing undersingle; an unstamped write is derived there and refused everywhere else (ADR-0131 D3/D9/D11) #15195, draft):objectql/src/engine.ts.
- feat(plugin-security)!: refuse a principal-less, non-system data-engine context (ADR-0096 D5 strict mode) #22297 (security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908):
- 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) is not in flight.
- The plan:
objectstack-fleet commented
on Oct 8, 2026 ContributorMore actionsos-dev-report
{
"issue": 15207,
"status": "done",
"branch": "claude/issue-15207-platform-global-no-column",
"pr": "#22331",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (subagent = the PM's id)",
"premise_still_valid": true,
"summary": "Draft PR #22331 (line 1 Fixes #15207, line 2 the claim's Clause-② line verbatim) makes the #12699 declaration total under ADR-0131 D7. The spec plan resolveInjectedSystemColumns(def, deployment?) takes the validated platformGlobalObjects as an optional input; ObjectQLPlugin.start() reads org-scoping FIRST (before loadMetadataFromService and the first schema sync) and installs it with SchemaRegistry.setDeploymentPlatformGlobalObjects(), which re-plans objects registered earlier in other plugins' init(); a declared object is registered and provisioned with no organization_id, no tenant index and systemFields.tenant false recorded (a materializeBaseLayer stamp with its write-side strip), so Layer 0 and the driver have nothing to scope. plugin-security's getObjectSecurityMeta fold and its platform-global boot line retire, with no fallback; the one reader moved to @objectstack/core (rules unchanged); OrganizationsPlugin declares providesServices org-scoping; a provider that registers outside init() with a different declaration fails the boot at kernel:ready by name. ADR-0087 D3 entry platform-global-object-organization-column-retired + one step-18 fragment + regenerated registry.ts; changeset major (objectql, plugin-security; pre mode is in) / minor (spec, core) / patch (organizations) with BREAKING banner and FROM/TO.",
"readings": {
"p1": "HOLDS, measured at 799eb00 before any edit through a booted ObjectKernel (ObjectQLPlugin over better-sqlite3, the real SecurityPlugin, a fixture org-scoping provider composed AFTER the objects plugin, declaring platformGlobalObjects: [qa_widget_registry]): the declared object was registered WITH organization_id and its table created with the column (columnInfo); security.getReadFilter(declared, member) answered undefined (the fold at security-plugin.ts getObjectSecurityMeta, third tenancyDisabled clause) while the sibling answered { organization_id: org_acme }; a system read of the declared table carrying tenantId org_acme returned only the org_acme row of two (the SQL driver's tenant arm). Control, no declaration: both walled.",
"p2": "HOLDS through ADR-0116's Phase 1/2 split plus the provider's providesServices declaration; NOT through a per-plugin edge (measured). Plan computed: SchemaRegistry.registerObject -> applySystemFields -> resolveInjectedSystemColumns, inside each registrant's init() via manifest.register, and again at start() (loadMetadataFromService, restoreMetadataFromDb) and later; columns fixed at ObjectQLPlugin.start() installRegisteredSchemas. org-scoping registered: OrganizationsPlugin.init(), which hard-depends on the engine; serve composes it after Auth and the app plugins, and the fixture confirmed the declared object was already registered when the provider initialized. Order: every init() completes before any start(), so the engine's start() sees a provider that registers in init() (now declared providesServices: ['org-scoping'], ADR-0116 D2) and no table exists yet; the registry re-plans what registered earlier. Measured: an engine-side optionalDependencies edge on the provider is a cycle (resolvePluginOrder over the two declarations throws 'Circular dependency detected: com.objectstack.engine.objectql'; CONTROL without the edge orders engine then organizations); an edge from every object registrant is an open-ended set. No plugin moved and no boot data step was added; the only new boot behaviour is the kernel:ready refusal for a provider that registered outside init() with a different declaration.",
"p3": "HOLDS. Absent key: registered shapes JSON-identical (registry with no install vs empty install), spec plan byte-identical over eight shapes, kernel pin with every column kept. Junk (bare string): the engine warns 'platformGlobalObjects' REFUSED exactly once and no object loses its column or wall; the reader's whole-key refusal and per-key independence are pinned in core.",
"p4": "HOLDS. git grep platformGlobalObjects at 799eb00: spec schema/docs/authorable-surface, the reader, plugin-security's consumer and boot log, and tests; no declarer. Every pin uses a fixture provider.",
"p5": "Measured. Author-time surfaces that compute the plan with no deployment name organization_id for a declared object: lint system-fields.ts (addressable names), spec import-mapping-target.ts, scripts/platform-object-tenancy-census.mjs, cli authoring-filter-judge. Runtime surfaces on the declaring deployment agree with it: the registry, the DDL, the /meta read exits (pinned through ObjectStackProtocolImplementation: governServedItem re-plans served bodies without the deployment, which the systemFields.tenant false record answers), the metadata bridge describe reads, lifecycle provenance (absent), and the field doors (INVALID_FIELD 400 / INVALID_FILTER). Stated in the plan's module doc, tenancy-posture.ts and the changeset. One composition-dependent one-shot surface: os migrate plan / apply compose the host config's plugins, not serve's posture-driven OrganizationsPlugin, so on a declaring deployment whose provider arrives only through serve a migrate plan reads no declaration and would add the column back (additive). PR Acceptance notes; carrier C10."
},
"tests": "Head 5322c2b unless noted (it merged origin/main dc4a5c6 via os-regen-merge.sh; regeneration wrote nothing). Through os-verify-lock, VERDICT command-exit 0 each: spec --project local 626 files / 18728 passed / 1 todo; spec --project repo step18-rationale-merge + conversions-major18-merge 21 passed; objectql --project local 385 files / 7562 passed; core 83 files / 2258 passed; organizations 11 files / 151 passed; plugin-security 183 files / 3835 passed / 45 skipped at 86db7e8 (later only a test-file type annotation and an unused objectql accessor changed; the touched plugin-security files re-ran green after). Typecheck exit 0 for core, objectql, plugin-security, organizations, spec, each with check:test-typecheck (ledgers held: core 4/4/4, objectql 40/234/65, spec 52/246/135, plugin-security 0, organizations 0). New pins: spec plan 5 cases, core reader 11, objectql registry 7, plugin-security kernel 8; rewritten: deployment-platform-global-exemption 8, tenant-layer0-verdict-end-to-end (declared sweep now matches 2 rows, was 1), tenant-layer0-verdict-on-operation (fixture re-registered as the registry registers it). Reverse verification via scripts/ablation-replace.mjs (hold mode, trap restore on EXIT INT TERM, absolute path): anchor "!(name !== '' && deploymentDeclaresPlatformGlobal" replaced so it never matches (anchor 1->0, blob 6e571966dd -> 88efcbbb14); spec rebuilt; ablation-dist-preflight found the marker in 6 built files; results: spec plan 2 red / 20 green (exactly the two declared-object pins), registry 5 red / 2 green (absent and opted-out controls green), kernel 4 red / 4 green (absent-key, junk-key, no-stand-down and late-provider controls green), end-to-end 1 red / 2 green. Restore: blob == HEAD 6e571966dd, git diff HEAD empty, whole tree clean; spec rebuilt (after ~45 min of lock contention behind a cli and a dogfood full run), preflight --absent: marker in none of 234 built files; same files green 22 / 7 / 11.",
"gates": {
"head": "5322c2b755",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at 5322c2b: 109 families (the claim-time list had 91; added: spec check:migration-registry, check:spec-changes, check:upgrade-guide, check:engine-double-contract, check:future-spec-major, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:where-matcher, check-scripts-symbol-anchors + self-test, check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:entry-guard, check:parse-guard, check:pnpm-filter-targets)",
"final_battery": "all 109 ran at 5322c2b, each exit captured before any pipe, all exit 0",
"reconciliation": "dispatch-gates --ran: '109 derived famil(ies) accounted for — 109 run, 0 NOT-MEASURED (a DERIVED zero — all 109 recorded an exit code and none of them is 3)', exit 0",
"first_pass_findings": "check:engine-double-contract exit 1 (the registry pin's three doubles absent from the pinned ledger; --write, committed); check:slot-lookup exit 1 (one untyped any service lookup in the kernel pin; typed); check:dts-closure exit 1, check:dual-build-cjs-loads exit 3, check:i18n exit 3 (unbuilt packages; green after a full turbo build, 72 tasks, 71 cached)",
"named_minimum": "check-adr-0087-registration: '[major+BREAKING+bang+clause-②-narrowing] registered platform-global-object-organization-column-retired (new here)'; check-changeset-no-major: pre mode (tag next), major exempt; check-empty-changeset exit 0; check-platform-object-tenancy-census exit 0 (84 objects, 49 in reach, 35 outside, unmoved); spec check:generated 15/15 up to date; check:migration-registry 400 semantic; check:api-surface unchanged; check:nul-bytes exit 0",
"extra_by_hand": "check:init-service-contract exit 0 (36 declared / 1 self-provided / 3 without a workspace provider); check:startup-registry-verdict exit 0 (45 seams, none recording a verdict the boot can contradict); node scripts/pm/check-governed-merges.mjs --pr 22331: NOT governed, 1742 changed lines",
"lint": "proven narrowing: eslint --no-inline-config --format json over the 21 changed .ts files = 21 results, 0 errors, 0 warnings; population from eslint.config.mjs packages/** and */ TS globs; invariance: the config states it enables no type-aware linting (no parserOptions.project, no typed rules), so no untouched file's verdict can move; full pnpm lint is CI's",
"not_measured": "none of the derived families. CI-only: full pnpm lint, cli layers (no cli file touched), dogfood, and the lanes dispatch-gates prints outside its derived total"
},
"line_budget": "vs merge base dc4a5c6: 23 files, +1479 / -263 = 1742 changed lines (PR object agrees per check-governed-merges); generated registry.ts +61, engine-double ledger +15",
"clause_2": "Claim line kept verbatim on PR line 2. Measured on the built closure: value yes holds (@objectstack/core gains readDeploymentOrgScopingEntitlement and two types; resolveInjectedSystemColumns gains an optional parameter with no new spec export, check:api-surface unchanged); arm stays narrowing (a declared object loses its column on the declaring deployment). Changeset: 'Clause-②: yes (narrowing)'.",
"c7_fate": "On a declaring deployment, each declared object's existing organization_id is ADR-0131 D10 fate 1 (column dropped): schema sync is additive, the boot drift report names it orphaned, the declarer (C10) owns the data step and backfill, then os migrate apply --allow-destructive; no boot step reads or writes it (D14). Stated in the PR body.",
"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 = POST /repos/objectstack-ai/objectstack/pulls (draft) giving #22331, body read back identical (18463 bytes); (2) label-write --assign os-litant = the issues/22331 assignees write, read back as matching; (3) this os-dev-report comment = POST /repos//issues/15207/comments via post-stamped. Plus git pushes (not REST writes). No labels written (the PR has a changeset; size/xl is automation's).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: C10 (cloud#1979, the control plane's composition) · noted, not filed, in the PR's Acceptance notes — os migrate plan / apply compose the host config's plugins, not serve's posture-driven OrganizationsPlugin; on a declaring deployment whose org-scoping provider arrives only through serve, a migrate plan reads no declaration and would add organization_id back to a declared object's table (additive sync). No in-repo declarer (P4), so no public door reaches it here · dedupe words: os migrate platformGlobalObjects, schema-migration composition org-scoping, declared platform-global column re-added",
"carrier: 承接者:无 · Acceptance notes only — author-time tools (lint addressable names, import mapper, tenancy census) name organization_id on a declared object; the declaring deployment refuses it as an unknown field. Inherent to a deployment input; stated in the plan's docs and the changeset"
],
"deviations": [
"Mechanism: the plan reads the declaration at ObjectQLPlugin.start() (ADR-0116 Phase 1/2 split + the provider's providesServices) and the registry re-plans objects registered earlier, rather than a soft dependency or an init-time requirement; measured that neither per-plugin edge can order the provider ahead of the registration-time plan (engine-side edge is a cycle). This is the seat lean recorded for item (4) ('registry reads the declaration at schema sync, after the provider is guaranteed'). The Zone 3 'requirer with no provider' pin has no subject in this design (the start-time read is optional, single posture has no provider); pinned instead: a provider registering outside init() refuses the boot at kernel:ready by name.",
"The registry records the plan's answer on a declared object's base layer as systemFields.tenant false (a materializeBaseLayer stamp with a write-side strip), so every existing reader (plugin-security's two clauses, the tenant index predicate, lifecycle provenance, the /meta read exits' injection pass) answers without a second reading of the declaration; the registry itself reads the declaration only through the spec plan (deploymentWithholdsTenant).",
"Files outside the claim's surface (each named in the PR body): packages/plugins/organizations/src/organizations-plugin.ts (providesServices — the provider declaration the claim's ordering bullet names; lane to re-declare); packages/core/src/security/deployment-org-scoping-entitlement.ts (+ .test.ts, index.ts) — the reader moved, both consumers import it; packages/objectql/src/federated-injected-column-readers.test.ts (two census rows the census requires); scripts/engine-double-contract.pinned.json (--write for the new pin's doubles).",
"New boot refusal at kernel:ready in ObjectQLPlugin (declaration changed after start()) — not in the dispatch's list; it is the validation half of the declared order.",
"Changeset grade major for @objectstack/objectql and @objectstack/plugin-security, per the claim ('major under pre mode if breaking'); .changeset/pre.json is mode pre, tag next.",
"origin/main moved 8 commits after the merged base dc4a5c6 (including #22186, #22197 and #22317 on registry.ts, engine.ts and security-plugin.ts). A no-commit merge probe auto-merges with no conflict; not merged in this round (the PR is at the tested head).",
"Commit trailers use the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named line; the PR footer is the AGENTS.md session-URL form, not the harness two-line form.",
"Killed only my own processes by recorded PID: a pre-merge plugin-security suite (superseded by the merge) and a queued lock wrapper whose rebuild had not run (its tests would have read the mutated dist).",
"Scratch probes (P1 kernel probe inside plugin-security src, the cycle probe in the scratchpad) were not committed; the P1 file was deleted before the first commit."
],
"files_changed": [
".changeset/15207-platform-global-no-organization-column.md",
"packages/spec/src/data/injected-system-columns.ts, injected-system-columns.test.ts",
"packages/spec/src/security/tenancy-posture.ts, tenant-layer0-verdict.ts (docs)",
"packages/spec/src/migrations/entries/semantic/18.platform-global-object-organization-column-retired.ts, packages/spec/src/migrations/registry.ts (regenerated + one step-18 fragment)",
"packages/objectql/src/registry.ts, plugin.ts, engine.ts (docblocks), registry-deployment-platform-global.test.ts (new), federated-injected-column-readers.test.ts",
"packages/core/src/security/deployment-org-scoping-entitlement.ts (moved from plugin-security), deployment-org-scoping-entitlement.test.ts (new), index.ts",
"packages/plugins/plugin-security/src/security-plugin.ts, auto-org-admin-grant.ts (docblock), platform-global-no-organization-column.test.ts (new), deployment-platform-global-exemption.test.ts (rewritten), tenant-layer0-verdict-end-to-end.test.ts, tenant-layer0-verdict-on-operation.test.ts",
"packages/plugins/organizations/src/organizations-plugin.ts",
"scripts/engine-double-contract.pinned.json"
],
"cleanup": "After this report is posted: node_modules removed and the worktree objectstack-issue-15207-global removed with git worktree remove (no --force); the branch stays on origin."
}objectstack-fleet commented
on Oct 8, 2026 ContributorMore actions✅ ACCEPT on the diff: PR #22331 at
5322c2b755(item (4), #12699 made total). Landing held for the at-tier contract review and CIdomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T17:45Z · holder of claim6061910188; the review of record for the report6065642320.Checklist (read on GitHub and in the PR's own diff, merge base
dc4a5c6308):- Form: draft, base
main, assigneeos-litant. Line 1 isFixes #15207, because this is the card's last item. Line 2 is the claim'sClause-②line, verbatim. - Size: 23 files, +1,479 / −263.
check-governed-merges: not governed. - ADR-0131 D7, read in the diff:
resolveInjectedSystemColumns(def, deployment?)takes the validatedplatformGlobalObjects. A declared object gets noorganization_id, no tenant index, andsystemFields.tenant: falserecorded on its base layer.ObjectQLPlugin.start()readsorg-scopingbefore the first schema sync. The registry re-plans objects that other plugins'init()registered earlier, so the columns are fixed only once.
- The stand-down retires with no fallback:
security-plugin.tsdrops theplatformGlobalObjects.has(object)clause of thegetObjectSecurityMetafold and its boot line. The one remaining clause issystemFields.tenant === false, which the plan now records. Nothing reads the declaration a second time. - The ordering is declared (ADR-0116):
OrganizationsPlugindeclaresprovidesServices: ['org-scoping'](D2), and everyinit()completes before anystart(). The dev measured that an engine-side soft dependency on the provider is a cycle ("Circular dependency detected"), with a control that orders cleanly without it. A provider that registers outsideinit()with a different declaration refuses the boot atkernel:ready, by name. That is the validation half of a declared order (ADR-0078). No plugin moved, and no boot data step was added. - Premises P1–P5 hold as measured. P1 is through a booted kernel:
- Before the change, the declared object had the column, the wall stood down, and the SQL driver's tenant arm still scoped it.
- P3: an absent key is byte-identical to today, over eight shapes. A junk key exempts nothing and is refused once.
- An authored
organization_idon a declared object stays the author's column, and the wall keeps scoping it, with a named warning. Accepted: the declaration removes only the platform's injected column. - Reverse verification: with the plan's read of the declaration ablated, exactly the declared-object pins go red (spec 2, registry 5, kernel 4, end-to-end 1), and every control stays green. Restore was proven by blob equality and a dist preflight.
- Grade:
majorforobjectqlandplugin-securityunder pre mode,minorforspecandcore,patchfororganizations, with the BREAKING banner and FROM/TO. ADR-0087 D3 entryplatform-global-object-organization-column-retiredat step 18, with the regeneratedregistry.ts. - C7's fate is stated in the PR body. On a declaring deployment, the existing
organization_idis fate 1: the boot drift report names it, the declarer (C10) owns the data step, and no boot step reads it (D14). - Gates: 109 derived and 109 run at the head, 0 NOT-MEASURED.
check:init-service-contractandcheck:startup-registry-verdictwere run by hand, and both exit 0.
Cross-lane: the files beyond the claim's regions are re-declared on #6367, #20966, #21118 and #6021 (
6065682328and the three beside it). No open PR touches them.Serial coupling with #22130 (PR #22215, protocol 18): this PR adds a step-18 semantic entry. Whichever of the two lands second regenerates
spec-changes.jsonand the protocol upgrade guide throughos-regen-merge.shbefore it enqueues.Out of scope, carried:
os migrate plan/applycompose the host config's plugins, notserve's posture-drivenOrganizationsPlugin. On a declaring deployment whose provider arrives only throughserve, a migrate plan reads no declaration and would add the column back (additive). There is no in-repo declarer, so no public door here reaches it. It is carried by C10 (cloud#1979) and named in the PR's Acceptance notes.- Author-time tools (lint addressable names, the import mapper, the tenancy census) name
organization_idon a declared object. That is inherent to a deployment input, and the plan's docs and the changeset say so.
- Form: draft, base
objectstack-fleet commented
on Oct 8, 2026 ContributorMore actionsos-dev-report
{
"issue": 15207,
"status": "done",
"round": "fix round on PR #22331 (claim 6061910188, seat ACCEPT 6065729426; the contract review PASS 6065958488 on 5322c2b is void by this push)",
"branch": "claude/issue-15207-platform-global-no-column",
"pr": "#22331",
"session": "session_01LAi5BVvQNiYzepSAcsoFLK (subagent = the PM's id)",
"premise_still_valid": true,
"summary": "One commit, 6b05507, fast-forward from 5322c2b; the PR stays draft. The step-18 D3 entry platform-global-object-organization-column-retired named the declaration by its tracker number inreason, which os migrate meta prints as why:. It now says 'Before this, the declaration stood the security layer's organization wall down...'. Every string field of the built entry (id, surface, replacement, reason, acceptanceCriteria) was checked against the test's own TRACKER_ID regex: all clean. The step-18 rationale fragment, which os migrate meta also prints as the hop rationale but the test does not hold, said it the same way and now reads 'the deployment's platform-global declaration'. The entry file's leading code comment keeps its tracker ids: the test header names surface, replacement, reason (why:) and acceptanceCriteria (verify:) as the printed block, and a source comment is printed nowhere. registry.ts regenerated by gen:migration-registry (2 lines). main not merged: GitHub reported the PR mergeable at 5322c2b, and the delta stays two files.",
"diff_this_round": "packages/spec/src/migrations/entries/semantic/18.platform-global-object-organization-column-retired.ts (1 line), packages/spec/src/migrations/registry.ts (2 lines: the generated copy of reason, and the rationale fragment); +3 / -3",
"tests": "At 6b05507, each through os-verify-lock and each exit captured before any pipe. (1) pnpm --filter @objectstack/cli exec vitest run --project integration test/migrate-meta-engine-guidance.test.ts: exit 0, 1 file / 3 passed, including 'prints every covered block verbatim, and no printed block names a tracker id'. It ran against a spec dist built from this head: the field check read dist/migrations MIGRATIONS_BY_MAJOR and found the new reason text. (2) pnpm --filter @objectstack/cli test (both projects): exit 1, 358 files passed and 3 failed, 4805 tests passed and 35 skipped. All 3 failures are the same prerequisite, packages/cli itself unbuilt: 'packages/cli is not built (./dist/index.js is absent)' in published-subpath-console.pin and published-subpath-hook-body.pin, and 'dist/commands/serve.js does not exist' in dev-standalone-self-heal.integration. After turbo run build --filter=@objectstack/cli (59 tasks), those 3 files: exit 0, 3 files / 33 passed. So every cli test file passed at this head; the 358 in the full run and the 3 in their re-run (a declared narrowing of the re-run to the files that failed on the prerequisite). (3) spec --project local src/migrations: exit 0, 4 files / 203 passed; spec --project repo step18-rationale-merge + conversions-major18-merge: exit 0, 21 passed. (4) check:migration-registry exit 0 ('registry.ts is current (400 semantic, 247 retired-key, 222 retired-def)'); node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 ('[major+BREAKING+bang+clause-②-narrowing] registered platform-global-object-organization-column-retired (new here)'); spec check:generated exit 0 ('All 15 generated artifacts are up to date', against a spec dist built at this head).",
"gates": {
"head": "6b055079e8",
"derivation": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) at 6b05507 derives the same 109 families as at 5322c2b; the change adds none",
"run_this_round": "the 40 families that read the touched files (every spec check:, check-adr-0087-registration and self-test, check-changeset-no-major, check-empty-changeset, check:doc-authoring, check:issue-citations, check-issue-citations, check:nul-bytes, check-spec-docblock-symbol-anchors, check:future-spec-major, check-keyed-text-bounds, check-comment-mask-, check:spec-parsed-alias, check:pm-changeset-deadline-census): all exit 0 at 6b05507",
"carried": "the other 69 families: all exit 0 at 5322c2b with --ran 109/109/0 NOT-MEASURED; this round's diff touches none of their inputs beyond the files the 40 above read"
},
"line_budget": "vs merge base dc4a5c6: 23 files, +1479 / -263 = 1742 changed lines (unchanged in size; this round +3 / -3)",
"root_cause_of_the_miss": "The round-1 report listed the cli layers as CI-only and not measured, on the rule that no cli file was touched. But migrate-meta-engine-guidance.test.ts reads the whole migration registry through @objectstack/spec, so a spec registry entry is a cli test input. Measured now. The derived gate battery could not name it: it is a test in the cli integration layer, not a gate family. Noted, not filed: nothing is broken now, and the rule is held by this very test in the merge queue's required set.",
"mcp_calls": "0",
"api_writes": "1 relay write this round: 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 5322c2b..6b05507), not a REST write. The PR body, labels and assignee are untouched.",
"pr_body_replacement": null,
"open_questions": [],
"out_of_scope_findings": [],
"deviations": [
"The step-18 rationale fragment was reworded too, beyond the entry the test hit. os migrate meta prints it, so it is author-shown, though the test does not hold it. Nine older step-18 fragments still carry tracker ids; they were left as they are (not this card's).",
"The full cli run's 3 prerequisite failures were re-run as those 3 files after building packages/cli, not as a second full suite (38 minutes under the lock).",
"main not merged this round: GitHub reported the PR mergeable at 5322c2b, and a merge would widen the delta the seat's contract review has to read.",
"Commit trailers: the AGENTS.md model-free pair."
],
"files_changed": [
"packages/spec/src/migrations/entries/semantic/18.platform-global-object-organization-column-retired.ts",
"packages/spec/src/migrations/registry.ts (regenerated)"
],
"cleanup": "After this report is posted: node_modules removed and the worktree objectstack-issue-15207-global removed with git worktree remove (no --force); the branch stays on origin at 6b05507."
}objectstack-fleet commented
on Oct 8, 2026 ContributorMore actions✅ ACCEPT on the delta: PR #22331 at
6b055079e8(fix round). The printed guidance names no tracker iddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T19:02Z · holder of claim6061910188; the review of record for the report6066999757. The ACCEPT6065729426stands for everything this delta does not touch. The contract review PASS6065958488names5322c2b755and is void at this head; a delta review is commissioned.The red on
5322c2b755, this PR's:Test Core (1/6),@objectstack/cli'sintegrationproject,migrate-meta-engine-guidance.test.ts:417: "platform-global-object-organization-column-retired: the printed guidance cites a tracker id: expected '#12699' to be undefined".- The new step-18 entry's
reason(printed aswhy:) named the declaration by its tracker number. - The round-1 report listed the cli layers as not measured because no cli file was touched. But that test reads the whole migration registry, so a spec registry entry is a cli test input.
The delta (1 commit, +3 / −3; read in the diff):
- The entry's
reasonnow says "Before this, the declaration stood the security layer's organization wall down…". - The step-18 rationale fragment, also printed by
os migrate meta, now says "the deployment's platform-global declaration". registry.tsis regenerated, and the meaning is unchanged.- The dev checked every printed field of the entry (
surface,replacement,reason,acceptanceCriteria) with the test's ownTRACKER_IDregex: all clean. The source comment keeps its tracker ids; it is printed nowhere. - Verified at the head:
- The failing test now passes.
- The full
@objectstack/clitest task passes, with every file green. Three files first answered "packages/cli is not built" and passed after the build. check:migration-registry,check:adr-0087-registrationand speccheck:generatedexit 0.- The 40 gate families that read the touched files exit 0. The other 69 are carried from
5322c2b755, because this delta touches none of their inputs.
Process note, recorded on the seat post: a PR that changes the migration registry runs the full
testtask of every package that reads the registry (cliabove all), not only the packages it touches.- The new step-18 entry's
objectstack-fleet commented
on Oct 8, 2026 ContributorMore actionsLanded: scope item (4), PR #22331 →
41d0d4038c. All four items have landed; this card closescompleteddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T19:48Z · holder of claim6061910188.- Landing: PR feat(objectql,plugin-security)!: an object a deployment declares platform-global gets no organization column on that deployment — the #12699 declaration made total (ADR-0131 D7) #22331 merged through the merge queue at 2026-10-08T19:47Z as
41d0d4038c, which has one parent (5ff7cbe364) and is an ancestor oforigin/main. ItsFixes #15207closed the card. - Content check: 23 files against the accepted head
6b055079e8.- 18 are blob-equal.
- The other 5 are files
mainalso changed while this PR was in flight:objectql/src/engine.ts,objectql/src/registry.ts,security-plugin.ts, the generatedmigrations/registry.tsandengine-double-contract.pinned.json. In each, this PR's own changed lines are identical in the squash's diff from its parent: 18, 252, 82, 61 and 15 lines, 0 missing. - In
migrations/registry.ts, the step-18 rationale sharesorder: 89with fix(spec,service-automation)!: an undeclared config key on 10 more builtin node types is refused at the build doors; one judge per type #22319's fragment, which the list's own rule allows: they render inidorder.
- Review of record: ACCEPT
6065729426and its delta6067029757; the at-tier contract review PASS6067095079on6b055079e8. CI was green there, with 3 skips, all in the roster. - What now holds (ADR-0131 D7):
- An object a deployment declares platform-global gets no organization column and no tenant index on that deployment.
- The engine reads the
org-scopingdeclaration atstart()before the first schema sync, re-plans objects registered earlier, and refuses a boot whose declaration changed afterstart().OrganizationsPlugindeclaresprovidesServices: ['org-scoping'](ADR-0116 D2). - feat(spec,security): OrgScopingEntitlement grows platform-global exemption + unbounded-admin suppression, consumed by Layer 0 arming #12699's Layer 0 stand-down is retired with no fallback.
- With the key absent, every plan is byte-identical to before.
- It ships
majorforobjectqlandplugin-securityunder pre mode,minorforspecandcore, andpatchfororganizations, with the BREAKING banner.
The card as a whole:
- (1) PR feat(platform-objects,service-automation,service-realtime)!: seven deployment-level tables lose their injected organization column, and reads need manage_platform_settings (ADR-0131 D7) #22107 →
1920cf3f83; - (2) PR feat(plugin-audit,plugin-security)!: sys_audit_log loses its injected organization column; tenant_id carries the organization a row is about and scopes organization readers (ADR-0131 D7) #22266 →
6729e107e8; - (3) PR feat(service-settings,platform-objects)!: the settings cascade's global rung moves to the tenant-less sys_platform_setting (ADR-0131 D7) #22166 →
c52bfb417b; - (4) this.
C6 of ADR-0131 is complete in this repository.
Carried elsewhere:
- 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): fate 1 for each declared object's existing
organization_idon a declaring deployment. The boot drift report names it; no boot step reads it (D14). Items (2) and (3) ship in C7's release. - C10, cloud#1979 (the control plane's adoption):
os migrate plan/applycompose the host config's plugins, notserve's posture-drivenOrganizationsPlugin. On a declaring deployment whose provider arrives only throughserve, a migrate plan reads no declaration and would add the column back (additive). This repository has no declarer, so no public door here reaches it. The declarer's composition owns it. This seat cannot reach that repository, so the pointer is written here and on the seat post. - Author-time tools (lint addressable names, the import mapper, the tenancy census) name
organization_idon a declared object. That is inherent to a deployment input, and it is stated in the plan's docs and the changeset.
Lesson recorded on the seat post: a PR that changes the migration registry runs the full
testtask of every package that reads the registry. On this PR,cli's guidance test caught a tracker id that no gate family derives.- Landing: PR feat(objectql,plugin-security)!: an object a deployment declares platform-global gets no organization column on that deployment — the #12699 declaration made total (ADR-0131 D7) #22331 merged through the merge queue at 2026-10-08T19:47Z as
- added 5 commits that reference this issue
on Oct 9, 2026
⛔ 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.