Skip to content

[repo:cloud] the hosted tenant runtime's owner admin grant (env-owner-admin-grant.ts) is written with no organization; it must be owned by the Default Organization before C8 stage 8c-2 retires the NULL-grant reader #24430

Closed

Description

@objectstack-fleet

Filing gate ④: a coordination node, the cloud half of objectstack C8 stage 8c-2 (#15212, epic #15194). Filed by the objectstack epic seat (epic:#15194, session_01Y5583bs2RDd53MH6kM1ca3) at the maintainer's word in that seat's chat: 「云端的锚点迁移 你建卡了吗,可以在 objectstack 创建 cloud 车道的卡片」. ⛔ Not a claim.

Reader who acts: the repo:cloud seat. It measures first, then builds in the cloud repo if a gap is found.

Dedupe:

Blocks: C8 stage 8c-2 on #15212. This card does not block on objectstack; the seat dispatches 8c-2 only after this card closes.

Why this exists

  • What cloud does. It confers a hosted tenant environment's owner platform standing by writing an admin_full_access grant row into the tenant runtime (env-owner-admin-grant.ts:67, :91, the cloud seat's measurement of 2026-09-03, pointer 5521222826 on platform-admin re-anchor follow-up (Choice 4B): config-anchor the single posture — first-user promotion becomes development-only fallback #11979). That row carries no organization.
  • The ruling. Ruling A′ on platform-admin re-anchor L8 (cloud seam): control-plane env injection + invite-only interaction — cloud seat leg, designed blind #11977 (record 5596500401, the maintainer's 「11977 同意」, 2026-09-09) fixed the anchor for a hosted tenant runtime, which is a single-posture kernel. It is what ADR-0131 D5 states: under single, the first-user promotion keeps writing the admin_full_access grant row, owned by the Default Organization, never NULL.
  • What objectstack is doing.
    • Stage 8b-0a has already moved the framework's own promotion row onto the Default Organization.
    • C8 stage 8c-2 retires the framework readers that still treat a NULL-organization grant as standing: resolve-authz-context.ts about :887–:938 and :1476–:1506, last-admin-guard.ts about :965–:1024, and ensure-default-organization.ts (plan 6251466182; amendment 6253205661 records this cloud rider).
  • The risk. If cloud's row is still NULL when 8c-2 lands, every hosted tenant's environment owner silently loses admin standing in their own environment. The cloud seat measured on 2026-09-03 that the write keeps succeeding and simply stops conferring, and that the writer swallows its own errors.
  • Reach: every hosted tenant environment.

Wanted (measure first)

  1. Measure on cloud main, at its framework pin.
    • What organization_id does the owner's admin_full_access row carry today?
    • Through which write path (the engine, or a driver or raw write)?
    • Under which posture does the tenant runtime boot?
  2. If the row is already owned by the tenant runtime's Default Organization, close this card completed with that evidence.
  3. Otherwise, make cloud's writer write the grant owned by the Default Organization (A′, D5). Prefer the framework's own path, the Default-Organization promotion that 8b-0a ships, over a second writer, if that path covers the hosted runtime.
    • Hosted runtimes that already exist need their NULL rows re-attributed. Under single, objectstack's ownership ceremony (os migrate organization-ownership) attributes a NULL grant to the Default Organization (ADR-0131 D10). Say whether cloud runs that ceremony on its tenant runtimes, or needs its own step.
  4. Make the writer loud. A failed or non-conferring owner grant must surface, not be swallowed.

Acceptance

Activity

  1. added
    priority:p1High: required for production / M2
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    on Oct 19, 2026
  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Amendment from the epic seat · session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T02:56Z. The maintainer asked in the seat's chat whether the session's newer rulings and discussion affect this card: 「刚才讨论的决裁影响这个卡片吗」. This comment records the answer. Nothing else on the card changes.

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round R4 · 2026-10-19T02:58Z · p1, jumped the queue on the maintainer's word in this seat's chat (「插队 #24430」) · no pm state → pm:dispatched
    Session: session_01Cbdek8SKSHxddGqTPZXdqX
    Account: os-zhuang
    Branch: none for round 1 (the measurement the epic seat waits on). A fix round, if owed, works in objectstack-ai/cloud on claude/issue-os24430-owner-grant-org
    Worktree: cloud-os24430
    Domain: repo:cloud
    Seat: repo:cloud#2
    File surface: round 1, none (readings only). A fix round: packages/objectos-runtime/src/env-owner-admin-grant.ts and its callers, tests beside, a changeset, in objectstack-ai/cloud. Stop on breach; explain in the report.
    Container & model: M (measure on a hosted tenant runtime at cloud's pins, then a cloud fix if owed), mode:subagent, model: default judgment tier (the seat's per-card call; the ceiling is not reached, reason: a measurement against ruling A′ / ADR-0131 D5 already decided)
    Clause-②: no
    Responsibility: cloud's own writer (packages/objectos-runtime/src/env-owner-admin-grant.ts) | the platform path: the single-posture Default-Organization promotion (8b-0a) and, after 8b, resolveSystemWriteOrganization | reach: every hosted tenant environment's owner
    Thread-read: 6258664098
    Serial constraints cleared: the seat's two in-flight devs (cloud#2861, cloud#3059) are in service-ai-studio; none touches objectos-runtime. This card runs as a third dev in flight on the maintainer's 「插队」.

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24430,
      "status": "reading",
      "branch": "none (round 1, readings only; a fix round works on claude/issue-os24430-owner-grant-org in objectstack-ai/cloud)",
      "pr": null,
      "session": "session_01Cbdek8SKSHxddGqTPZXdqX (subagent of the repo:cloud#2 seat; the seat's id)",
      "premise_still_valid": true,
      "shas": "objectstack-ai/cloud main 0133bc280132bafdd67b884910e537ab85323f9f; its framework pin .objectstack-sha ec749fbb44b5e6015d3f205c63dfa47cfb6ef445 (2026-10-18 12:43Z, frozen pin tree, built); objectstack main read for 8b-0a and #24325: db1f1fd20fba543408b209746e566b48373a4622. Cloud tag v2.1.0 pins be076dad28a0.",
      "boot_method": "The objectos-runtime test harness, not run-stack.sh: ArtifactKernelFactory.create end to end (the factory's real composition: PlatformObjectsPlugin, the environment-organization plugin, AuthPlugin with an auth base secret, SecurityPlugin, the owner/org seed block), SQLite in memory as the tenant's own database, a control-plane stand-in serving the environment row provisioning writes (ownerSeed + orgSeed) and an empty artifact. Modelled on environment-owner-member-seed.e2e.test.ts. Rows read from the tenant database through the kernel's engine, plus a wrapper on the tenant driver's create that recorded write order, the row as handed to the driver, the organizations physically present at each grant write, and resolveSystemWriteOrganization's decision at that moment. Three shapes: provisioned (orgSeed declared), ownerSeed without orgSeed, and two simulations (the ceremony's attribution; 8b's derived stamp). The probe was an uncommitted scratch file, deleted before the rig was torn down.",
      "step1": {
        "1_organization_id": "NULL. Freshly provisioned tenant: the owner's admin_full_access row is organization_id null (created_at 03:07:26.682Z), the only admin_full_access row in the database. Same in the no-orgSeed shape.",
        "2_write_path": "The engine. env-owner-admin-grant.ts:87-93 calls dataEngine.insert('sys_user_permission_set', ..., organization_id: null at :91, ...) with context isSystem (:75), after a find at :81-84. dataEngine is the ObjectQL engine on both entry paths: the sys_user-insert middleware gets kernel.getService('objectql') (artifact-kernel-factory.ts:2262-2263, call at env-owner-admin-grant.ts:151); sso-exchange passes authSvc.getDataEngine() (auth-proxy-plugin.ts:513-516), which is ctx.getService('data'). Measured: data === objectql true, getDataEngine() === objectql true; the driver's create was reached from _ObjectQL.createWithAutonumberResync inside the engine's middleware chain. No driver or raw write.",
        "3_posture": "single. Measured resolveTenancyPosture() = single; tenancy service requestedPosture single, posture single, isolationActive false, degraded false. The factory reads no posture knob (artifact-kernel-factory.ts:1346-1367). apps/objectos worker.ts and wrangler.toml set none (0 hits). run-stack.sh:1036 starts objectos with OS_MULTI_ORG_ENABLED=false.",
        "4a_through_engine": "Yes (item 2).",
        "4b_default_org_exists_at_write": "Yes. Boot order: the mirror organization is written by createEnvironmentOrganizationPlugin's start (mounted at artifact-kernel-factory.ts:1088-1092, ahead of AuthPlugin at :1107; insert at environment-org-seed.ts:138, start :200-208, a failed write refuses the boot) inside kernel.bootstrap() (:2050). The grant is written after bootstrap, in the owner-seed block (:2219): seedProjectOwner (:2275, sys_user insert at environment-owner-seed.ts:142) fires the middleware registered at :2262-2263. The sso-exchange path runs per request, after boot. Readings: driver write order sys_organization (seq 1, t=475 ms), sys_user (seq 3, 586 ms), grant (seq 5, 602 ms); organization created_at 03:07:26.563Z, grant 03:07:26.682Z; organizations in the database at the grant's write = [the mirror org]; resolveSystemWriteOrganization at that moment = derived, the mirror org. No-orgSeed shape: '[default-org] created the Default Organization' at 03:07:27.044Z, grant at 03:07:27.098Z, decision derived to that org.",
        "5_owner_standing_through_row": "Yes, and only through it. hasPlatformAdminStanding = true, rung PLATFORM_ADMIN, positions include platform_admin. The pin's anchor is the organization-less row only (resolve-authz-context.ts:1529 and :1546-1551 at ec749fbb, the !ownGrant branch). The same row re-attributed to the Default Organization drops the owner to MEMBER at this pin (measured).",
        "6_writer_swallows": "Yes. try/catch to logger?.warn?.('... failed (non-fatal)') at env-owner-admin-grant.ts:77 and :95-97, declared at :67-68 and :123-124. A silent return with no log at :76 when the engine lacks find/insert: sso-exchange passes null when getDataEngine is missing (auth-proxy-plugin.ts:513-515). The registration wrapper only warns (artifact-kernel-factory.ts:2261-2270). Measured: an insert that throws resolves with threw=false and one warn line. The writer never reads back whether its row confers standing."
      },
      "item7_existing_runtimes": "Cloud does run the ceremony, as a boot step, not in a deploy: TenantV18UpgradeGate wraps the kernel factory (tenant-v18-upgrade-gate.ts:1-24), and step 3 of the sequence runs 'os migrate organization-ownership --apply' (tenant-v18-upgrade.ts:63-66; cli tenant-v18-upgrade-cli.ts:351). It runs once per environment, before its first v18 kernel. A verified record makes every later run a no-op (:146-149). An environment born at v18 is recorded verified (bornAtV18) and runs no platform command (:157-161). At cloud's pin the ceremony does NOT re-attribute a provisioned tenant's owner grant. The inventory gives sys_user_permission_set fate attribution with no anchors (organization-ownership-inventory.ts:567), so under single the row goes to the Default Organization. At ec749fbb the plan finds that organization only by slug = 'default' (organization-ownership-plan.ts:1275-1283). A provisioned tenant's one organization is the control-plane mirror carrying the cloud organization's slug (environment-org-seed.ts:138-143). So the owner's NULL grant is unattributable (plan :1192) and stays NULL as advisory, not held against the verdict (tenant-v18-upgrade.ts:728-735, :1373-1381). The exception is a tenant with no orgSeed, whose organization is the framework's 'default'. There the ceremony attributes the grant, and at this pin and at v2.1.0's pin (neither carries 8b-0a: is-ancestor exit 1, content check 0 hits) the attributed row confers nothing (measured: MEMBER) until the owner's next console Open. That Open re-inserts a fresh NULL row, because the writer's check only looks for NULL rows (measured: a second row, back to PLATFORM_ADMIN). How many tenants have this shape was not measured. objectstack main (#24325, 51ad8e4f, not in the pin) names the Default Organization as slug 'default', else the sole organization, and re-runs a checkpointed attribution. But cloud's gate never re-runs the ceremony on a verified or bornAtV18 environment, and ruling C on #24352 only governs what certifies. So nothing on cloud re-attributes an existing NULL owner grant today, and 8c-2 would make those owners lose standing silently. Cloud needs its own step. The natural carrier is a TENANT_BACKFILL_STEPS entry in the runTenantBackfills lane (tenant-v18-upgrade.ts:1968; it records its own sys_migration row, is retried at every build until verified, and has the cloud#2900 / cloud#2990 shape). It would attribute NULL admin_full_access owner grants to the environment's one organization.",
      "item8_paths_forward": {
        "a_framework_8b0a_promotion_covers_hosted": "No. 8b-0a (b2e78701, #24373, after the pin) changes only the promoted row's owner and the readers. The selection is unchanged: the declared OS_PLATFORM_OWNER_EMAIL, which a tenant runtime cannot have (ruling A-prime), else the oldest authenticable human. The owner seed writes no sys_account. Measured with cloud's grant removed: a member who signed up with a login was promoted ('first user promoted to platform admin: member@... basis: oldest-authenticable'), and the owner stayed MEMBER. So cloud's writer must stay. What the framework path supplies is the organization and the readers: at main the resolver anchors a NULL or Default-Organization grant (grantAnchorsPlatformStanding, resolve-authz-context.ts:912-919 on main), and the holder scan reads both. The Default Organization is slug 'default' else the sole organization, which matches the mirror. Two consequences: (1) the writer's swallow is worse than a lost owner, because a failed owner grant leaves the slot open and the framework then elevates the first member with a login; (2) a Default-Organization-owned row must never be written at a pin before 8b-0a. There it confers nothing (measured), and the pin's holder scan reads NULL rows only (bootstrap-platform-admin.ts:561, :581 at ec749fbb), so the framework would promote someone else.",
        "b_after_8b_resolveSystemWriteOrganization": "The pin carries it, byte-identical to main (git diff --quiet exit 0): packages/core/src/security/system-write-organization.ts:108-126. The amendment's :108-135 runs 9 lines past the function's end. Today the engine never asks it for this object: engine.ts:7074 at the pin (:7183 on main) exempts sys_user_permission_set, which is absent from PLATFORM_OBJECT_TENANCY (platform-object-tenancy.ts:136-217; :261-264), on main too. Once 8b removes that exemption, cloud's write qualifies: its context carries no tenantId, and the row's explicit organization_id null does not count as carrying an organization (engine.ts:7086). The decision would be derived to the sole organization, measured at every grant write in both shapes. The engine threads it as DriverOptions.tenantId (engine.ts:15809-15820), and injectTenantOnInsert fills a null slot (sql-driver.ts:15653-15664). So the row is stored owned by the Default Organization and is never refused while the database holds one organization (cloud#2848 shipped inside the v18 move ddfc6e11, so no v18 database holds two from that cause). Measured post-8b side effect: the first Open stores the Default-Organization row, and every later Open re-inserts, because the check at env-owner-admin-grant.ts:85 counts only NULL rows. The unique index refuses it ('Duplicate record refused on sys_user_permission_set') and the writer swallows the refusal. A loud writer would turn that into an error on every console Open. At ec749fbb that stamped row confers no standing (MEMBER, measured), so 8b reaches cloud safely only in a pin that also carries 8b-0a. That holds by construction, because 8b waits on 8b-0a."
      },
      "recommendation": "Both of the amendment's answers are yes, so 8b does not have to wait on cloud: when cloud's pin carries 8b, the engine stamps cloud's write with the Default Organization and does not refuse it. What must wait for the cloud fix is cloud's own pin bump past 8b. The amendment's shrink ('loud writer plus a pin') does not hold as measured, and this report flags the conflict instead of picking one reading silently: (i) without a new idempotency check, a loud writer raises an error on every console Open after cloud's 8b bump; (ii) existing environments keep NULL owner grants that nothing on cloud re-attributes, so acceptance line 2 fails, and 8c-2 would remove those owners' standing. Recommended fix round, open question 1 option B. (1) Bump the pin to an objectstack main SHA carrying b2e78701 (8b-0a) and 51ad8e4f (#24325), through scripts/bump-objectstack.sh. (2) env-owner-admin-grant.ts: the idempotency check reads the anchor the framework reads (a NULL or Default-Organization admin_full_access grant), and the writer writes the Default Organization explicitly, resolved through the framework's exported resolveDefaultOrgId (@objectstack/plugin-auth, slug 'default' else the sole organization), never a second definition. Make it loud: a failed write, an unresolvable Default Organization, a missing engine, and a written grant that hasPlatformAdminStanding does not confirm all surface at error level and are returned to the callers (artifact-kernel-factory.ts:2261-2270, auth-proxy-plugin.ts:512-517), each of which decides its own refusal. (3) Add one TENANT_BACKFILL_STEPS entry that attributes existing NULL owner grants to the environment's one organization. It is due before 8c-2 (the card's close), not before 8b. File surface: packages/objectos-runtime/src/env-owner-admin-grant.ts and its test, the two callers named above, tenant-v18-upgrade.ts plus one new backfill module and its test, .objectstack-sha with the lockfile through the bump script, and a changeset. The backfill module and the pin are outside the claim's declared file surface. Ordering hard line: the explicit Default-Organization write never lands in a cloud tree whose pin lacks 8b-0a.",
      "summary": "Round 1, readings only, no edit committed, no PR. On cloud main 0133bc28 at framework pin ec749fbb, a freshly provisioned hosted tenant runtime boots under single and writes the owner's admin_full_access grant with organization_id NULL. The write goes through the ObjectQL engine after the Default Organization (the control-plane mirror) already exists. The owner's PLATFORM_ADMIN standing derives from that NULL row alone, and the writer swallows its own failures. 8b would stamp the write rather than refuse it. A loud writer plus a pin is still not enough: the idempotency check only recognizes NULL rows, nothing on cloud re-attributes existing NULL grants, and a Default-Organization row confers nothing at a pin before 8b-0a.",
      "tests": "Rig: /home/user/rig-os24430 created as ordered (a detached worktree of /home/user/cloud at origin/main 0133bc28, plus a symlink to the frozen pin tree); it did not exist when the run started. pnpm install --frozen-lockfile exit 0. Closure build via os-verify-lock, OS_VERIFY_LOCK_SLOT=issue-os24430, command pnpm --workspace-concurrency=2 --filter '@objectstack/objectos-runtime^...' build: VERDICT command-exit 0, held 21s. Probe via os-verify-lock, command (in packages/objectos-runtime) pnpm exec vitest run --maxWorkers=2 src/zz-os24430-probe.e2e.test.ts. Three runs: VERDICT command-exit 0 each, 'Tests 2 passed (2)', '3 passed (3)', '4 passed (4)'. The probe asserts nothing; it boots and records, and the readings are quoted in step1, item7 and item8. Ancestry, two legs each, on the shallow objectstack checkout: b2e78701 (8b-0a) is-ancestor ec749fbb exit 1, control 0af559e8 (2026-10-16, deeper) exit 0; 51ad8e4f (#24325) is-ancestor ec749fbb exit 1, same control exit 0; b2e78701 is-ancestor be076dad (v2.1.0's pin) exit 1, control exit 0; ec749fbb is-ancestor origin/main exit 0. Content leg: grantAnchorsPlatformStanding has 0 hits in both pin trees. NOT MEASURED: the same boot at a pin carrying 8b-0a, because this container has no built objectstack main tree; the readings there come from source on main and 8b-0a's own pinned tests (its commit message). NOT MEASURED: staging or production tenant rows, because no tenant database was read.",
      "gates": "None owed and none run. This round commits no diff, so dispatch-gates --commands derives no family. No PR, so no check-runs. Writes made while this report was produced: none besides this comment.",
      "mcp_calls": "0",
      "api_writes": "1 — this os-dev-report comment on objectstack-ai/objectstack#24430, through the fleet-write relay (repository_dispatch, executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/issues/24430/comments). Zero label, claim or assignee writes. Reads were REST GETs only (the card, the comments 6258664098, 6258684134, 5521222826, 5596500401, 6251466182, 6251599171, card #24352).",
      "deviations": "(1) The ordered rig did not exist, so I created it exactly as described, then tore it down: node_modules removed, git worktree remove without --force exit 0, symlink removed, pin trees untouched. (2) A scratch probe test file was placed, uncommitted, in the rig worktree for the measurement and deleted before teardown (git diff HEAD clean, exit 0). (3) The dispatch's preset recommendation ('nothing but loud plus a pin if both answers are yes') conflicts with the readings; both answers are yes, and the conflict is raised as open question 1 rather than resolved silently. No stack was started, so there was nothing to stop.",
      "open_questions": [
        {
          "question": "Fix-round scope. Both of the amendment's answers are yes, which per the amendment shrinks the card to 'loud writer plus a pin'. The readings say that shape fails the card's acceptance line 2 (environments provisioned before the change) and makes a loud writer fire on every console Open after cloud's 8b bump. Which scope does the fix round take?",
          "options": [
            "A: as the amendment says, a loud writer plus a pin bump past b2e78701. About 60-120 cloud lines plus a pin round. Measured consequences: a duplicate-refusal error on every console Open after cloud's 8b bump; existing NULL owner grants stay NULL and go dark at 8c-2.",
            "B: A, plus an idempotency check that reads the framework's anchor (NULL or Default Organization), plus an explicit Default-Organization write through the framework's exported resolveDefaultOrgId, plus one TENANT_BACKFILL_STEPS entry attributing existing NULL owner grants. Estimated 250-450 source and 300-500 test lines; the writer changes land before cloud's 8b bump, and the backfill before 8c-2. Can ship as two PRs.",
            "C: B, but leave the organization to 8b: drop the explicit organization_id null and do not resolve the Default Organization in cloud. Fewer lines, but the card cannot close until cloud's pin carries 8b, and every environment provisioned between cloud's 8b-0a bump and its 8b bump writes a NULL row the backfill must sweep again."
          ],
          "recommendation": "B. On the four axes. Real business need: reach is every hosted environment's owner, whose standing derives only from this row today (measured); A measurably fails acceptance line 2, while B covers new and existing environments, and C covers them later. Long-term soundness: in B the producer names its owner (ADR-0131 D1 and D5, the treatment 8b-0b gave the framework's own producers), there is one definition of the Default Organization (the framework export, not a cloud copy), and existing rows move through cloud's established backfill lane; A leaves rows that 8c-2 turns dark silently, and C ties the card's close to 8b. Preventing AI-written mistakes: B makes a failed or non-conferring grant loud, without the false alarm A would raise on every Open; an alarm that always fires teaches people and agents to ignore it, and writing an explicit null is the 'NULL is a state' shape that gets copied. No startup-stage sprawl: B adds no gate and no tool, only one step in an existing lane, at about 3x A's lines; C is slightly smaller. Within these axes the tension is lines (A) against the acceptance being met (B). Self-check: axis 1 alone picks B, and no other axis flips it."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review and ruling · round 1 → round 2 · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T03:23Z

    Thread-read: the newest comment is round 1's os-dev-report 6258895784. This seat accepts it as a reading; round 1 made no edit and no PR.

    For the epic seat, the step-1 answer: both of the amendment's questions (6258664098) answer yes. 8b does not wait on cloud.

    • The write goes through the ObjectQL engine.
    • The tenant runtime's one organization exists before the grant is written: the control-plane mirror, or the framework's default when no orgSeed is declared. Measured at cloud main 0133bc28 on framework pin ec749fbb.
    • So with one organization in the tenant database, 8b's resolveSystemWriteOrganization stamps cloud's write with that organization; it does not refuse it.
    • What waits is cloud's own pin move past 8b. Round 2 below lands before that move, or with it.

    Spot-checked by the seat at cloud main 97b124df, in packages/objectos-runtime/src/env-owner-admin-grant.ts:

    • :85 reads only NULL rows: .some((l) => !l.organization_id).
    • :91 writes organization_id: null.
    • :95-97 catch the error and warn.
    • :76 returns silently when there is no engine.

    Also checked:

    Ruling on open question 1: B

    The amendment shrinks the card to "a loud writer plus a pin". That shrink rests on "8b stamps the row by itself", and as measured this holds for new rows only:

    • Acceptance line 2 is not met. Nothing on cloud re-attributes an existing NULL owner grant. For a provisioned tenant the ceremony leaves it unattributable, and verified or bornAtV18 environments never re-run the ceremony. At 8c-2 those owners lose standing, silently.
    • A loud writer alone fires on every console Open after cloud's 8b bump. The check reads only NULL rows, so the writer inserts again, and the unique index refuses the duplicate.

    The card's acceptance and the amendment's shrink conflict, so the seat acts on the stricter one: the acceptance as written. Option C ties the card's close to 8b and sweeps the same rows twice. The maintainer may veto this ruling.

    Round 2 shape (ruled; premises are verified first)

    PR 1: the pin and the writer, one PR, in objectstack-ai/cloud on claude/issue-os24430-owner-grant-org.

    • The pin. Move .objectstack-sha to an objectstack main sha that carries b2e78701 and 51ad8e4f, through scripts/bump-objectstack.sh (lockfile included).

      • The pin's fallout is in scope only where a test or path reads it.
      • ⛔ If the fallout reaches a package's behavior beyond the owner-grant path and the tests that read it, stop and report a fork. The seat then splits the move into its own pin-round card.
    • The writer.

      • The idempotency check reads the anchor the framework reads: an admin_full_access grant whose organization is NULL or the Default Organization.
      • The writer stores the Default Organization explicitly. The organization is resolved through the framework's exported resolver.
      • Premise: such an export exists at the new pin. If it does not, report a fork. ⛔ Never write a second definition in cloud.
    • Loud. These four cases are logged at error level and returned to the two callers (artifact-kernel-factory.ts near :2261-2270, auth-proxy-plugin.ts near :512-517):

      • a failed write;
      • a Default Organization that cannot be resolved;
      • a missing engine;
      • a written grant that hasPlatformAdminStanding does not confirm.

      ⛔ Sign-in and boot still succeed when the grant fails. This round adds no new refusal; acceptance line 3 asks for a report, not a refusal.

    • ⛔ Hard ordering line: the explicit Default-Organization write never lands in a tree whose pin lacks b2e78701. PR 1 meets it by carrying both.

    PR 2: one TENANT_BACKFILL_STEPS entry, after PR 1, due before 8c-2.

    • It attributes existing NULL admin_full_access owner grants to the environment's one organization (acceptance line 2).
    • A tenant database with 0 or 2+ organizations is reported and left as it is. ⛔ Never guess.
    • ⛔ It lands only in a tree whose pin carries 8b-0a.

    Close: both PRs merged, then the hand-off comment on objectstack#15212 with the PRs and the pin. 8c-2 waits on this card's close, as before.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Release note from the epic seat · session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T04:25Z. The maintainer has ruled that 18.0 ships for fresh installs only (ruling record 6259608309 on #15194).

    What that means for this card:

    • Existing hosted tenants stay on 17.x until the 18.x release that carries the upgrade path. In 18.0, os migrate organization-ownership refuses to run.
    • This card's backfill of existing NULL owner grants (acceptance line 2) moves to that 18.x release. C8 stage 8c-2, which this card gated, also moved to 18.x.
    • For fresh tenants on 18.0, cloud's own measurement (6258895784) shows 8b stamps the owner grant with the tenant runtime's one organization. Nothing else is owed for 18.0.
    • The repo:cloud#2 seat may re-order round 2. Its writer half (loud failure, Default-Organization check) is still worth landing. Its pin move to 18.0 is only useful for fresh tenants.
  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24430,
      "status": "done",
      "branch": "claude/issue-os24430-owner-grant-org (objectstack-ai/cloud)",
      "pr": "https://github.com/objectstack-ai/cloud/pull/3063",
      "session": "session_01Cbdek8SKSHxddGqTPZXdqX (subagent of the repo:cloud#2 seat; the seat's id)",
      "premise_still_valid": true,
      "pin": {
        "chosen_sha": "584b4225e1fcbaf5e8bed14d9c280d6347a897ff (objectstack main, 2026-10-18 19:58Z, #24383). It is the newest main sha that carries b2e78701 and 51ad8e4f and stops before eebaf3b5 (C8 stage 8a). It is NOT the newest green sha: that was db1f1fd2, whose move breaks behavior beyond the owner-grant path (see fork_readings_at_db1f1fd2 and open question 1).",
        "ancestry_legs": "On the shallow objectstack checkout:\n- b2e78701 is-ancestor 584b4225: exit 0.\n- 51ad8e4f is-ancestor 584b4225: exit 0.\n- ec749fbb (the old pin) is-ancestor 584b4225: exit 0.\n- eebaf3b5 is-ancestor 584b4225: exit 1. Control leg on the same checkout and target: ec749fbb, older and deeper, exit 0.\n- Content leg: packages/core/src/security/tenant-layer0-verdict.ts (8a's new module) is absent at 584b4225.\n- 584b4225 is-ancestor db1f1fd2: exit 0.\n- For the db1f1fd2 attempt: b2e78701, 51ad8e4f and 8baeda52 (#24412) are each an ancestor of db1f1fd2, exit 0.",
        "objectstack_ci_at_chosen_sha": "Lint & Type Check: success. CI: failure, only on Dogfood Regression Gate (shards 1/3 and 2/3), packages/qa/dogfood running pnpm run test. b2e78701 reads the same red, and d8e11b58 (20:59Z) is green again. No pre-8a main sha after b2e78701 has a green CI run: 741aa85d has no run; c76e8492, cf16a0d1, 5e979073 and 1eae21f9 were cancelled; 584b4225 and b2e78701 failed.",
        "bump": "scripts/bump-objectstack.sh 584b4225e1fc --no-commit, with its verify on, under os-verify-lock (slot issue-os24430). Framework turbo build --force 77/77, cloud build 21/21, cloud typecheck 22/22, '✓ Verified: framework + cloud build clean and cloud typechecks at 584b4225e1fc'. VERDICT command-exit 0, the lock held 522 s. Lockfile unchanged (BOUNDARY_DEPS=()). Committed as 691fa099 with the AGENTS.md §11 trailers.",
        "fork_readings_at_db1f1fd2": "Bumped first to db1f1fd2 (commit 1964974f), verified (VERDICT 0, 611 s), then ran cloud's whole farm at that pin-only commit.\n- objectos-runtime: 9 of 2041 red in 2 files.\n  - remote-tenant-scope-hosted.e2e.test.ts, 8 cases (b) and (c) on both the REMOTE and LOCAL faces. First error: 'expected [ Legacy Co, Mine Co, …(1) ] to deeply equal [ Legacy Co, Mine Co ]' at :215, then 'expected 200 to be 404' at :221, :229 and :238. GET, PATCH and DELETE of another organization's row answer 200.\n  - unique-dedupe-tenancy.e2e.test.ts, 1 case at :185: 'expected [ 3Y3o…, …(2) ] to deeply equal [ 3Y3o…, …(1) ]'.\n  - Cause: eebaf3b5 (C8 stage 8a, #24389). Its Clause-② table says that under single, a caller naming an organization now gets driver scope none. On a hosted tenant kernel, cloud#2573 pins that driver scope as the only organization fence.\n- service-ai-studio: 1 of 2392 red, metadata-schema-depth.test.ts 'object cannot move a level', 'expected 4 to be 3' at :163.\n- Turbo summary: 37/38 tasks, Failed: @objectstack/service-ai-studio#test (objectos-runtime ran separately).\n- Control legs at the old pin ec749fbb, in a separate worktree over the frozen pin tree (read only): the two objectos-runtime files 22/22 passed, the studio file 6/6 passed. Isolated re-runs at db1f1fd2: 9 failed of 22, and 1 failed of 6.\n- Everything else was green at db1f1fd2.\n- No boot refusal seen. tenant-v18-upgrade's suites were green at both pins, so no measured ceremony verdict change. Note that #24403 (8c-0), which makes an unattributable organization-less row block certification, is in db1f1fd2 but not in 584b4225.",
        "riders": {
          "objectstack#24377 Q1 A": "Not carried. No commit on objectstack main implements it (no 24377 or self-registration commit in the last 200), and apps/cloud's unscoped-control-plane-tenant-wall.test.ts is green unchanged at both pins. :2453-2455 and :2475-2476 untouched.",
          "objectstack#24412": "Not carried at 584b4225 (8baeda52 is later). It was carried at db1f1fd2, and there too no cloud test read it: no cloud test asserts the caller's role after transfer_ownership or the copy, and service-tenant team.page.test.ts (name, target, bodyExtra only) was green at both pins."
        },
        "pin_fallout_absorbed": [
          ".objectstack-sha: ec749fbb44b5 → 584b4225e1fc. Lockfile untouched.",
          ".spec-changes-ack.json: 3 entries, verdict not-used (commit 8c61c777). audit-spec-changes --framework at 584b4225 exited 1 on three NEEDS-REVIEW surfaces:\n  - declared sharingRules are no longer seeded into sys_sharing_rule (5e979073);\n  - a main page region's width is narrowed to 'full' (3a734e08, two catalog entries).\nCloud declares no stack sharingRules, reads no sys_sharing_rule row, and its main regions declare width 'full' or none: 0 hits for small, medium or large. After the acks the audit exits 0."
        ]
      },
      "acceptance": {
        "harness": "packages/objectos-runtime/src/env-owner-admin-grant.e2e.test.ts. It runs ArtifactKernelFactory.create end to end, modelled on environment-owner-member-seed.e2e.test.ts and round 1's probe, on SQLite in memory with a control-plane stand-in. Two shapes: provisioned with orgSeed (the Default Organization is the mirror org_owner_grant_24430, slug owner-workspace, the database's only organization), and with no orgSeed (the framework's organization with slug default, the only one). Framework pin 584b4225.",
        "case_1_fresh": "Both shapes pass. The owner's admin_full_access rows equal exactly [Default Organization id]. resolveUserAuthzGrants rung = PLATFORM_ADMIN, and hasPlatformAdminStanding = true. Zero [EnvOwnerAdminGrant] warn or error lines. The framework's bootstrap logs 'already_have_admin', adminUserId usr_owner_grant_24430: its holder scan finds the owner through the Default-Organization row.",
        "case_2_second_open": "Both shapes pass. The writer, called with kernel.getService('auth').getDataEngine() (the engine sso-exchange passes), returns {ok: true, outcome: already-anchored, organizationId: the Default Organization}. The rows are identical before and after. A second cold boot (factory.create again on the same database) leaves the rows identical. Zero error lines. Through the real HTTP handoff, sso-exchange-first-entry.e2e's second exchange also writes nothing.",
        "case_3_pre_change_env": "Both shapes pass. The test deletes the owner's Default-Organization row and inserts a NULL row through the driver (the old writer's shape). With no row the owner has no standing (hasPlatformAdminStanding false), which shows case 1's standing came through that row. With the NULL row the rung is PLATFORM_ADMIN and the standing is true again. The writer returns already-anchored with organizationId null, and a second cold boot keeps exactly the one NULL row. This is not the card's acceptance line 2; PR 2 covers that.",
        "case_4_insert_throws": "Both shapes pass. A driver that refuses admin_full_access inserts:\n- the kernel build completes, and the owner's sys_user row is seeded;\n- no grant row is written;\n- exactly one error line: [EnvOwnerAdminGrant] with entry owner-row-insert, reason write-failed, userId and organizationId = the Default Organization;\n- zero grant warn lines;\n- the writer returns {ok: false, reason: write-failed} with the driver's message, and does not throw.\nSign-in side (env-owner-admin-grant.test.ts, AuthProxyPlugin's sso-exchange mounted on stubs): an insert that throws answers 302 with the session cookie and logs one error line (entry sso-exchange, reason write-failed, environmentId). With no data engine it answers 302 and logs one engine-unavailable error line.",
        "sso_exchange_http": "sso-exchange-first-entry.e2e.test.ts (the real tenant-kernel fixture over HTTP) pinned the old organization-less grant. It now asserts one grant owned by the tenant runtime's one organization, plus hasPlatformAdminStanding true for the creator: 9/9."
      },
      "summary": "PR 1 is open as a draft on objectstack-ai/cloud: https://github.com/objectstack-ai/cloud/pull/3063, assignee os-zhuang. It moves cloud's framework pin to 584b4225 and changes the owner-grant writer. The pin carries 8b-0a and #24325 and stops before 8a. The newest green sha, db1f1fd2, turns 10 cloud tests red beyond the owner-grant path: 8a removes the driver organization fence on hosted tenant kernels under single, and one studio depth pin moves. Those readings are the fork for the next pin round; at 584b4225 the whole farm is green. The writer stores admin_full_access owned by the Default Organization, resolved through plugin-auth's exported resolveDefaultOrgId. Its idempotency check reads the framework's anchor (a NULL, blank or Default-Organization row). Each failure is returned as a value, and sso-exchange and the kernel build log it at error level without refusing. The premise held: resolveDefaultOrgId is exported from the plugin-auth entry through export * at src/index.ts:93 (the seat's grep missed the re-export).",
      "tests": "All heavy runs went through os-verify-lock, slot issue-os24430, with NODE_OPTIONS=--max-old-space-size=4096 and turbo --concurrency=2.\n- Pin census before the writer: at 584b4225, turbo run test over the whole farm, 39/39 tasks (VERDICT 0). objectos-runtime 2041/2041, service-ai-studio 2385 passed and 7 skipped, service-cloud 4626, apps/cloud 816, apps/objectos 424, service-tenant 1003 passed and 2 skipped, service-ai 1536, security-enterprise 409, objectos-ee 189, heartbeat 113, plus the smaller packages.\n- Final union at 2099b31a: turbo run typecheck test over ...@objectstack/objectos-runtime and ...@objectstack/service-ai-studio, 24/24 tasks (VERDICT 0). objectos-runtime 2072/2072 in 149 files, apps/objectos 424, apps/cloud 816, objectos-ee 189, service-ai-studio 2408 passed and 7 skipped (with cloud#3060 and cloud#3061 merged from main). Every typecheck green, including check:test-typecheck.\n- The same union at the final head 8c61c777: 24/24, all cached, the same task hashes (the last commit touches only .spec-changes-ack.json, which no task reads). VERDICT 0.\n- The first union at cf9d5153 was red on 1 test, sso-exchange-first-entry.e2e:238, which pinned the old NULL grant. It was updated in 2099b31a and passes 9/9.\n- New tests: env-owner-admin-grant.test.ts (34 cases: the writer's unit cases, the reporter, the middleware, and the sso-exchange caller) and env-owner-admin-grant.e2e.test.ts (10 cases: cases 1 to 4 in both shapes), 44/44.\n- Ablation with objectstack scripts/ablation-replace.mjs in wrap mode. The anchor check was set back to read NULL only: '        if (owners.includes(organizationId)) {' became '        if (false && owners.includes(organizationId)) {'. Anchor count 1 → 0, blob 73cad49d → ff356e88. Result: 3 of 44 red, namely case 2 in both shapes and the unit case for the Default-Organization anchor. Received: {ok: false, reason: write-failed, message: 'inserting the admin_full_access grant failed: Duplicate record refused on sys_user_permission_set: a unique constraint already holds these values. No record was written.'}. Restore: blob after restore 73cad49d = HEAD's blob, 'git diff HEAD' empty, and git hash-object = git rev-parse HEAD:path = 73cad49d.",
      "gates": "Local, at the final head 8c61c777.\n- cloud has no dispatch-gates; the check:* families were derived by hand from each script's scan roots against the changed files (.objectstack-sha, .changeset/, .spec-changes-ack.json, packages/objectos-runtime/src). Each exit code was captured before any pipe, and every one is 0: control-bytes, route-envelope, constant-time-secrets, ee-manifest, link-deps, framework-anchor, retired-framework-deps, kernel-ready-registration, walled-declares-owner, declared-twins, ee-boot-refusal-declared, turbo-pin-inputs, workspace-dist-preflight, ee-feature-consumers, ai-route-sweep, action-predicate-roots, pins, objectui-pin-divergence (advisory printed, see out_of_scope) and pin-staleness.\n- Left out because their roots do not read the changed files: adr-numbering, comment-mask (self-test), image-git-hygiene, wrangler-image-pin, workflow-credentials, simulation-switches, ee-plugin-wiring, migration-has-invoker, acceptance-ports, docs-error-codes, worker-entry-typecheck, ai-model-pin, dts-gating, outbound-suggestion, graph-snapshot and golden-journey (self-tests), hotcrm-publish, and driver-turso (dev-local).\n- audit-spec-changes: --self-test exit 0. --framework against the framework worktree at 584b4225: exit 1 before the acks, exit 0 after (611 surfaces, 305 acknowledged).\n- Control-byte self-scan of the changed files: 0 hits.\n- PR check-runs on 8c61c777 when the report was written: typecheck-and-gates in_progress, console-boots (pin smoke) in_progress, 'Build and exercise EE image (no publish)' in_progress, build-and-test skipped (fast tier, by design; the full tier runs in the merge queue), and 'Decide whether this PR needs the EE image build' success. The CI verdict is NOT MEASURED here; the seat reads it.",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, executed by fleet-write.yml as objectstack-fleet[bot]):\n- (1) pr_create, run 39200204140: POST /repos/objectstack-ai/cloud/pulls (draft) and POST /repos/objectstack-ai/cloud/issues/3063/assignees (os-zhuang). Read-back: 12281 bytes sent, 12281 stored, identical.\n- (2) this os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24430/comments.\nZero label, claim or assignee writes on the card. git push to the branch, not a REST write: 5 pushes (the empty-branch probe, then 1964974f, 691fa099, 5b788905 and 8c61c777). Reads were REST GETs only.",
      "deviations": [
        "The sha is 584b4225, not the newest green main sha (db1f1fd2), which the dispatch preferred. db1f1fd2's move turns 10 tests in 2 packages red beyond the owner-grant path. 584b4225 meets the ruling's requirement (carries b2e78701 and 51ad8e4f) with zero farm fallout, but its objectstack CI is red on the framework's own Dogfood Regression Gate. The fork is raised as open question 1 and is not decided silently. The branch history keeps the db1f1fd2 bump (1964974f) followed by the re-pin (691fa099); a squash merge flattens them.",
        "The bump script ran with --no-commit, verify on, and I committed the pin myself, so the commit carries the AGENTS.md §11 trailers (the script's own commit carries none). Its turbo calls take no concurrency knob (turbo 2.11.5 has no TURBO_CONCURRENCY) and ran at turbo's default under the lock; the script was not edited.",
        "A control worktree (cloud detached at 97b124df, with a read-only symlink to the frozen pin tree ec749fbb) was created under /home/user/rig-os24430b/ctl to run the control legs. Its closure was built in cloud only. The pin tree was never written (git status empty) and the worktree was removed with the rig.",
        "The writer returns a fifth reason beyond the ruled four: user-unspecified, for an empty user id or owner email. It was also a silent return before, and it never fires from the two callers.",
        "The diff reaches past the claim's literal file surface in three files. environment-owner-seed.test.ts and sso-exchange-first-entry.e2e.test.ts pinned the old NULL write and now pin the Default-Organization write. .spec-changes-ack.json is pin fallout read by CI's Changed-spec-surface guard.",
        "Two workflow-only commits landed on cloud main after my merge commit (bd1848b3, f006c91c; .github/workflows only). They were not merged in: git merge-tree is clean, and the merge queue stacks the PR on main.",
        "The PR diff is 891 lines (+820/-71), under 3000, so there is no 维护者速读 section.",
        "The final head's test results are turbo cache replays of the 2099b31a execution, with identical task hashes."
      ],
      "open_questions": [
        {
          "question": "Which framework sha should PR 1 pin? The dispatch preferred the newest green main sha, db1f1fd2. Its move turns cloud red beyond the owner-grant path: 8a (eebaf3b5) gives a hosted tenant kernel under single no driver organization fence, so remote-tenant-scope-hosted.e2e (8 cases) and unique-dedupe-tenancy.e2e (1) go red, and the studio schema-depth pin moves (1). PR 1 as opened pins 584b4225, the newest pre-8a sha, which carries both required commits. Its farm is green, and its objectstack CI is red only on the framework's dogfood gate.",
          "options": [
            "B (as opened): PR 1 pins 584b4225. Landable now, with farm 39/39 and the PR's fast-tier CI. The 8a move and the studio pin go to the pin-round card the seat opens anyway: any cloud pin past eebaf3b5, 8b's included, hits them. Cost: a cloud pin on a framework sha whose own CI was red on Dogfood Regression Gate (framework-internal; green again from d8e11b58), and the next pin round still owes the 8a decision.",
            "A: as the dispatch's stop rule reads. Re-pin PR 1 to db1f1fd2 or newer, by a merge after the pin-round card lands, and hold PR 1 until that card absorbs the 8a fallout. Cost: PR 1, and PR 2 behind it, wait on a decision about the hosted tenant org fence that has no owner yet. The writer cannot land without b2e78701 (the hard ordering line), so this card waits too.",
            "C: pin b2e78701 itself, the minimum. It has the same red dogfood CI as 584b4225 and carries fewer intervening fixes. It is unmeasured on cloud's farm."
          ],
          "recommendation": "B, on the four axes.\n- Real business need: the card's reach is every hosted owner's standing, and 8c-2 waits on this card. B measurably meets acceptance cases 1 to 4 now (green farm, ablation red as predicted). A ties a measured, landable fix to an unrelated open security decision.\n- Long-term soundness: B is no workaround. It is a forward move to a main sha the ruling permits, the 8a fallout stays visible as its own round, and nothing in cloud is loosened to absorb it.\n- Preventing AI-written mistakes: B lands the stricter writer sooner (no explicit null, loud failures). The 8a fork is a contract change whose cloud answer must be decided, not absorbed into this PR, which is what the stop rule protects.\n- No startup-stage sprawl: B adds no gate or tool, only three not-used acks. A adds a wait.\nThe tension: B pins a sha whose framework CI was red on a framework-internal gate. Axis 1 picks B, and no axis flips it. The seat may veto; the re-pin under A is a merge plus one bump commit."
        },
        {
          "question": "For the pin-round card (out of this PR): how does cloud meet 8a's widened driver scope on hosted tenant kernels? At db1f1fd2, GET, PATCH and DELETE /api/v1/data on a tenant kernel answer 200 for another organization's row (cloud#2573's pins), on both the REMOTE and LOCAL faces.",
          "options": [
            "A: accept 8a's contract. A tenant database holds one organization (cloud#2848: the mirror is the database's one organization), so re-pin cloud#2573 and cloud#2685 to it and prove that no hosted tenant database holds 2 or more organizations. Cost: one measurement round, about 2 test files, and a fleet census of organization counts.",
            "B: keep an organization fence on hosted tenant kernels by booting them under a walled posture. Cost: a posture change for every tenant runtime. It reaches the owner grant (walled anchors no grant: standing becomes config-derived) and the ceremony. A large change.",
            "C: ask objectstack for a scoped exception for hosted tenant kernels. Cost: a framework contract carve-out against 8a's 'one predicate' design."
          ],
          "recommendation": "A, decided on the pin-round card with its own measurement. It follows 8a's contract (one predicate) instead of adding a cloud-side fence, and the one-organization-per-tenant-database invariant already exists (cloud#2848). B collides with this card's anchor, because under a wall no grant anchors standing. I did not measure the fleet's organization counts; this recommendation rests on the readings above, not on a census."
        }
      ],
      "out_of_scope_findings": [
        "class: b · reach: exception: security. At objectstack db1f1fd2 (8a, eebaf3b5), the hosted tenant kernel's data door answers 200 to GET, PATCH and DELETE of another organization's crm_account row, where cloud#2573 pins 404; measured in remote-tenant-scope-hosted.e2e.test.ts on both faces, and 22/22 green at ec749fbb. Seam: spec: none (DriverOptions.tenantId projection) → runtime: objectql engine.ts buildDriverOptions. Not reached at this PR's pin 584b4225. Carrier: the pin-round card the seat splits per the ruling; routed as open question 2. Dedupe words: 8a driver scope single hosted tenant kernel cloud#2573 remote-tenant-scope-hosted organization fence",
        "carrier: the same pin-round card · noted, not filed. service-ai-studio metadata-schema-depth.test.ts:163 'object cannot move a level' reads 4, not 3, at db1f1fd2 (a spec size change in 584b4225..db1f1fd2; the commit was not bisected). Green at 584b4225 and at ec749fbb.",
        "carrier: none (承接者:无) · noted, not filed. packages/objectos-runtime/src/tenant-v18-upgrade.ts:724 and :732 cite sys_sharing_rule's declared-rule mirrors as advisory rows the boot re-creates. From 5e979073 (in this pin) the boot no longer writes them and the ceremony deletes them, so the example is stale prose that no code reads. tenant-v18-upgrade's suites are green.",
        "carrier: none (承接者:无) · noted, not filed. check:objectui-pin-divergence prints its advisory at 584b4225: framework-pin objectui 0707b4ae3712 against cloud .objectui-sha 4dfdebdf5ffe (exit 0; the script calls it legitimate in the middle of a pin train)."
      ],
      "cleanup": "The dev server was never started. node_modules was removed in all three rig worktrees (0 left). git worktree remove without --force exited 0 for /home/user/rig-os24430b/cloud, /home/user/rig-os24430b/ctl/cloud and /home/user/rig-os24430b/objectstack. The ctl symlink was removed with rm on the link, and /home/user/rig-os24430b was removed. The frozen pin tree ec749fbb is intact (git status: 0 entries). The shared checkouts were never edited, stashed or checked out."
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling confirmed · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T04:39Z

    The maintainer confirmed this seat's ruling B (6258957472) in the seat's chat, verbatim: 「24430 同意。」

    So the veto window is closed and the round-2 shape stands:

    • PR 1, in flight: the framework pin past b2e78701 and 51ad8e4f, plus the owner-grant writer. The writer reads the NULL-or-Default-Organization anchor, writes the Default Organization explicitly through the framework's exported resolver, and reports failures loudly without refusing sign-in or boot.
    • PR 2, after PR 1, before 8c-2: the TENANT_BACKFILL_STEPS entry for existing NULL owner grants.

    8b does not wait on cloud (the step-1 answer above).


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review · round 2 · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T04:41Z

    Thread-read: the newest comment is round 2's os-dev-report 6259782253. PR 1 is cloud#3063, at head 8c61c777.

    Open question 1, the pin sha: ruled B, PR 1 pins 584b4225.

    Open question 2, 8a on hosted tenant kernels: routed to the maintainer as cloud#3065 (needs-user-decision, p1).

    PR 1 review: the writer, the four acceptance cases and the ablation match the ruling (6258957472, confirmed by the maintainer 6259794416). The seat ACCEPTs on cloud#3063 once its checks finish green (typecheck-and-gates, console-boots, the EE image build). PR 2, the backfill step, is dispatched after PR 1 merges.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → cloud#3063 comment 6260233006 · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T05:18Z. Ready and auto-merge in this act (head 8c61c777). Part of: this card stays open for PR 2, the backfill step.

  11. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    LANDED (PR 1) · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T05:36Z · stays pm:dispatched (PR 2, the backfill step, is next)

    reading value
    cloud origin/main 4a5c8d26 (cloud#3063), through the merge queue; the merge commit names no model
    what landed cloud's framework pin ec749fbb → 584b4225, which carries 8b-0a (b2e78701) and #24325 (51ad8e4f) and stops before 8a. The hosted tenant runtime's owner grant is written owned by the Default Organization, resolved through @objectstack/plugin-auth's resolveDefaultOrgId. The idempotency check reads the framework's anchor (NULL or Default Organization). Failures come back as values and are logged at error level; sign-in and boot still succeed
    records ACCEPT on cloud#3063 6260233006; this card's pointer 6260251131
    deploy ships with the next cloud deploy-staging run carrying 4a5c8d26; new environments get the Default-Organization grant from then on

    Left on this card:

    • PR 2: one TENANT_BACKFILL_STEPS entry attributing existing NULL owner grants to the environment's one organization (acceptance line 2). It is due before 8c-2, and the seat dispatches it next.
    • The hand-off on objectstack#15212 with both PRs and the pin, at this card's close.
    • Cloud's pin past 8a (eebaf3b5) waits on cloud#3065: the maintainer ruled that a hosted tenant database holds exactly one organization, and the enforcement round is in flight.
  12. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24430,
      "status": "done",
      "branch": "claude/issue-os24430-owner-grant-backfill (objectstack-ai/cloud)",
      "pr": "https://github.com/objectstack-ai/cloud/pull/3070",
      "session": "session_01Cbdek8SKSHxddGqTPZXdqX (subagent of the repo:cloud#2 seat; the seat's id)",
      "premise_still_valid": true,
      "premise_checks": "Verified on cloud origin/main before the first edit (eb534b82, then 72042a4c after cloud#3068 and cloud#3069 landed). The pin is 584b4225 and carries b2e78701 (8b-0a), so the ordering line holds. TENANT_BACKFILL_STEPS had three entries, and nothing on cloud re-attributed a NULL admin_full_access grant. resolveDefaultOrgId at the pin (plugin-auth tenancy-service.ts:225) answers the organization with slug 'default', else the sole one, else null; it does not refuse a database that holds two organizations when one of them is slug 'default'. So the step counts the organizations itself before it asks the resolver. The grant's declared index (plugin-security sys-user-permission-set.object.ts:272) is (user_id, permission_set, organization_id) with unique: 'organization', which is NULL-safe.",
      "acceptance": {
        "harness": "packages/objectos-runtime/src/tenant-admin-grant-organization.e2e.test.ts, in the shape of PR 1's env-owner-admin-grant.e2e.test.ts. It runs ArtifactKernelFactory.create end to end on SQLite in memory, with the gate's own tenantKernelPorts and the sys_migration ledger they write, at framework 584b4225 (independent depth-1 clone, built 76/76). The pre-PR-1 environment: the boot's Default-Organization row is deleted (the owner then reads hasPlatformAdminStanding false), and the owner's NULL admin_full_access row is written through the driver (the owner then reads PLATFORM_ADMIN through it). 12 cases, 12/12 at 1f7e76ed.",
        "case_1_provisioned_before_pr1": "Both shapes pass. The lane (runTenantBackfills on tenantKernelPorts) gives the same row, ups_legacy_owner_grant_24430, the one organization: status verified, pending 1, report scanned 1, stamped 1, organizations 1, attributed [{was: null}], confirmed [owner], failures 0. The owner's admin rows equal [{that id, the organization}]. resolveUserAuthzGrants rung PLATFORM_ADMIN, hasPlatformAdminStanding true. The ledger row reads verified, applied, blocking 0, advisory 0, phase backfilled. One info line: 'admin-grant-organization: gave 1 of 1 organization-less admin_full_access grant(s) the organization ORG, 1 user(s) keep platform-admin standing (objectstack-24430-admin-grant-organization verified)'. A second boot (factory.create again on the same database, the step on the new kernel's ports) returns already-verified with ledgerRow kept. The ledger row is equal before and after (last_run_at included), the grant is unchanged, standing is PLATFORM_ADMIN, and the count reads 0.",
        "case_2_both_shapes": "orgSeed: the mirror org_owner_grant_backfill_24430, slug owner-workspace, the only organization. no orgSeed: the framework's organization with slug default, the only one. Case 1's readings are identical in both shapes.",
        "case_3_two_organizations": "The second organization is written through the driver, as cloud#2573's suite does, so cloud#3068's engine guard (landed, merged in) is not asked. Nothing is written: the NULL row stays, and the owner stays PLATFORM_ADMIN through it. Status failed, ledgerRow recorded, pending 1, report held 1, heldRows [{the row, the owner}], organizations 2, organizationId null, attributed [], removed []. The reason: 'the database holds 2 organization(s), not exactly one, so its 1 organization-less admin_full_access grant(s) are left as stored and none is attributed (details.report)'. The ledger row reads verified false, applied false, blocking 1, phase failed. The next boot runs the step again, gets the same reason, and writes nothing. Unit cases cover 0 organizations, and 2 organizations of which one is slug 'default': nothing is written and the resolver is never asked for an answer it would give.",
        "case_4_no_null_rows": "A fresh environment after PR 1 (its owner holds the Default-Organization row): status nothing-to-backfill, pending 0, ledgerRow recorded. The ledger row reads verified, applied false, blocking 0, phase nothing-to-backfill. The grants are unchanged.",
        "duplicate_decision": "Measured, then removed. On the booted harness, the index takes a NULL admin_full_access row beside the owner's Default-Organization row. It refuses a second NULL row for the same user (driver create throws). It refuses the NULL row updated onto the organization: engine.update rejects with code DUPLICATE_RECORD, status 409. It also takes a blank ('') row beside the NULL one, because the index keys NULL and blank apart. So when the user already holds the set in the organization, removing the organization-less duplicate is the only way to leave no organization-less grant. The step removes it through the engine, by id, as the platform, and reads it back, keeping the organization's row (keptId). The removed row as stored goes into the step's ledger row (details.report.removed). With a NULL and a blank row, the NULL row is attributed and the blank one removed as its duplicate. Measured: case 5 removed the NULL row, the owner kept PLATFORM_ADMIN, and the row reads verified; the blank case attributed the NULL row, removed the blank one, and standing held.",
        "standing_check": "For each user it writes for, the step reads hasPlatformAdminStanding before and after that user's rows are written. A drop from true to false restores the user's rows (an attribution is set back to its spelling, a removed row is written back) and stops the step: failures 1, notReached counts later users' rows, and the row stays unverified. Measured in case 6: the owner's organization row expired (valid_until 2020), so only the NULL row anchors standing. The removal dropped standing to false. The row was written back, standing read true again, the error names the user, the row and the restore, and the ledger row reads verified false, blocking 1. A user with no standing before the write is an advisory (withoutStanding), not a failure."
      },
      "summary": "PR 2 is open as a draft on objectstack-ai/cloud (#3070, assignee os-zhuang, head 1f7e76ed, origin/main merged after cloud#3068 and cloud#3069 landed). It adds admin-grant-organization, the fourth entry in TENANT_BACKFILL_STEPS, with its own ledger row objectstack-24430-admin-grant-organization. The step gives every NULL or blank admin_full_access grant the database's one organization, which resolveDefaultOrgId names, and only when the database holds exactly one organization; with none, or two or more, it writes nothing, stays unverified, and names the count. It removes an organization-less duplicate where the user already holds the set in that organization, because the unique index refuses the update. It confirms each user's platform-admin standing before and after, and restores and stops on a drop. tenant-verdicts.sh now reads the fourth row, because its go line claims to cover every backfill row the runtime records.",
      "tests": "All heavy runs went through os-verify-lock, slot issue-os24430, with NODE_OPTIONS=--max-old-space-size=4096, workspace concurrency 2 and --maxWorkers=2. The framework is an independent depth-1 clone at 584b4225, fetched read-only from /home/user/objectstack, turbo build spec then everything but docs, --concurrency=2, own cache dir: 76/76, VERDICT 0, 410 s. The final chain ran at the merged head 1f7e76ed (cloud pin 584b4225):\n- pnpm install --frozen-lockfile exit 0.\n- Build of the objectos and objectos-ee closures and objectos-runtime: VERDICT 0.\n- objectos-runtime: 153 test files (equal to its 153 tracked *.test.ts), 2130/2130 passed, VERDICT 0.\n- apps/objectos: 41 files, 424/424, VERDICT 0. It has 46 tracked *.test.ts; the other 5 are the standalone tsx entrypoints its vitest.config.ts excludes (boot-smoke, crm-bundle, multi-environment-e2e, multi-environment-bundles, provisioning), each run by its own CI step, and none reads the backfill lane (git grep: 0 hits).\n- apps/objectos-ee: 21 of 21 files, 189/189, VERDICT 0.\n- Typecheck objectos-runtime, objectos, objectos-ee: VERDICT 0 each, check:test-typecheck OK (0 files held in debt).\n- tenant-v18-upgrade's suites and their neighbours, per file (json reporter, VERDICT 0, 14 files, 344 tests): tenant-v18-upgrade.test.ts 137/137, -gate 47/47, -cli 19/19, -history-seq 17/17, -retired-tables 19/19, -backup-locks 1/1, -release-database.e2e 3/3, tenant-backfills-after-seed-replay.e2e 2/2, tenant-admin-grant-organization.test.ts 18/18 (new), tenant-admin-grant-organization.e2e.test.ts 12/12 (new), env-owner-admin-grant.test.ts 34/34, its e2e 10/10, environment-one-organization-guard.test.ts 7/7, environment-second-organization-refused.e2e 18/18.\n- apps/cloud/test/ops-v18-kit.test.ts: 17/17, VERDICT 0. tenant-verdicts.mjs --self-test: OK, 125 passed and 0 failed, 14 of them new (the probe string, PENDING and STOPPED lines, and on real SQLite files the NULL, blank and owned cases with two negative controls). tenant-verdicts.sh --self-test passed through the shell; the kit portability scan passed (14 files).\n- Existing lane tests updated for the fourth step: tenant-v18-upgrade.test.ts (the four-step order, its outcome and log line, absent lines, plus 3 new step-level cases) and tenant-v18-upgrade-gate.test.ts (the fakes' new port, and the outcome list).\n- Ablation with objectstack scripts/ablation-replace.mjs in wrap mode. The attribution 'await engine.update(ADMIN_GRANT_TABLE, { id, organization_id: organizationId }, SYSTEM);' became '/* ablation: the attribution is skipped */ void organizationId;'. Anchor x1 -> x0, replacement x0 -> x1, blob d5626597 -> 3b9d2725. The child run of the e2e (VERDICT command-exit 1): 5 failed, 7 passed of 12. The red ones are case 1 in both shapes and case 1's second boot in both shapes (the row read unverified), plus the blank-spelling case. Received: attributed [], failures 1, stamped 0, error 'the row reads back without the organization, so it was changed under the write; the next build reads it again'. Restore: blob after restore d5626597 = blob at HEAD d5626597, git diff HEAD empty, git status 0 entries. No dist/ step is owed: the e2e imports the step's source relatively, not through a package export.",
      "gates": "Local, at 1f7e76ed. cloud has no dispatch-gates, so the check:* families were derived by hand from each script's scan roots against the changed files (packages/objectos-runtime/src, scripts/ops/v18, the runbook, .changeset). Each exit code was captured before any pipe, and every one is 0: control-bytes (2772 files), route-envelope, constant-time-secrets, ee-manifest, link-deps, retired-framework-deps, kernel-ready-registration, walled-declares-owner, declared-twins, ee-boot-refusal-declared, turbo-pin-inputs, workspace-dist-preflight, ee-feature-consumers, ai-route-sweep, action-predicate-roots, simulation-switches, dts-gating, acceptance-ports, ai-model-pin, docs-error-codes.\nLeft out because they read only the pins or files this diff does not touch: framework-anchor, pins, pin-staleness, objectui-pin-divergence (no pin moved), adr-numbering, image-git-hygiene, wrangler-image-pin, workflow-credentials, ee-plugin-wiring, migration-has-invoker, worker-entry-typecheck, and the self-test-only and dev-local families.\nControl-byte self-scan of the 12 changed files: 0 hits. The PR body and the commits name no model.\nPR check-runs on 1f7e76ed when this report was written: typecheck-and-gates in_progress; build-and-test skipped (the fast tier, by design; the full tier runs in the merge queue); 'Build and exercise EE image (no publish)' skipped; 'Decide whether this PR needs the EE image build' success. The CI verdict is NOT MEASURED here; the seat reads it.",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, executed by fleet-write.yml as objectstack-fleet[bot]):\n- (1) pr_create, run 39212489245: POST /repos/objectstack-ai/cloud/pulls (draft forced by the relay) and POST /repos/objectstack-ai/cloud/issues/3070/assignees (os-zhuang). Read-back: 10536 bytes sent, 10536 stored, identical. My own GET read it back the same: draft, assignee os-zhuang, body equal to the file sent.\n- (2) this os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24430/comments.\nZero label, claim or assignee writes on the card. git push to the branch, not a REST write: 5 pushes (the empty-branch probe, then 95072898, 1fc5e506, ae03d3e8 and 1f7e76ed). Reads were REST GETs only (comments 6259794416, 6258895784, 6259782253, 6260467704; cloud#3068 state and files; cloud#3070 and its check-runs).",
      "deviations": [
        "The diff goes past 'one TENANT_BACKFILL_STEPS entry' in three places. (a) scripts/ops/v18/lib/tenant-verdicts.mjs, tenant-verdicts.sh and the v18 runbook. The verdict reader's go line claims every backfill row the runtime records is verified or does not apply, and this PR adds a fourth row; left out, an environment holding two organizations would read VERIFIED there. The two earlier backfills (cloud#2961, cloud#2996) taught it their row in the same PR. (b) tenant-verdicts.sh --help printed lines 4-20 of a header that ran past them, cut mid-sentence before this PR. This PR adds a header line, so the range is now 4-23, the whole header. (c) TenantBackfillStep gains an optional heldText, so a held row's reason names its cause (the organization count). Without it the generic 'left for a ruling' text stays, and the three existing steps read as before.",
        "'A row whose standing would drop is a failure that stops the step' is implemented as: write the user's rows, confirm, and on a drop restore them and stop. The restore is my reading of 'would drop'; it keeps the owner's standing intact after the failure (measured in case 6).",
        "'Each affected user still answers hasPlatformAdminStanding true': a user who answered false before their rows were written has no standing to keep. That user is listed as an advisory (withoutStanding), not a failure, and the rows are still attributed.",
        "A removed duplicate is recorded in the step's own ledger row after the run, as stored, not exported to the ledger before the delete as steps 0, 4 and the preflight do. The generic runTenantBackfill records once, after port.run(). The row is a duplicate of a grant the user keeps in the organization, and its standing is confirmed after the delete. If recording that row then failed, the removed row's content would survive only in the engine's own history (the object declares trackHistory).",
        "The step reads the organization count through the engine (engine.count of sys_organization, as the platform) before it asks resolveDefaultOrgId. That count is the ruling's 0 or 2+ guard, not a second definition of the Default Organization.",
        "The e2e runs case 1 through the whole lane (runTenantBackfills on the real kernel ports), with no-op runners for the framework's activity and comment backfills, which this package does not depend on. The second boot is a second factory.create on the same database, followed by the step on that kernel's ports. TenantV18UpgradeGate.startBackfills itself was not driven end to end in the booted e2e; its lane is held by tenant-v18-upgrade-gate.test.ts (the outcome list now names the fourth step).",
        "The rig's framework is an independent depth-1 clone at 584b4225 under /home/user/rig-os24430c/objectstack, fetched read-only from /home/user/objectstack and built with its own turbo cache. Nothing was written under /home/user/objectstack-pin/. The clone, the turbo cache, the worktree (removed without --force, after its node_modules) and the rig directory are all removed.",
        "The PR diff is 1486 lines (+1429/-57), under 3000, and touches no governed path, so there is no 维护者速读 section."
      ],
      "open_questions": [],
      "out_of_scope_findings": [],
      "cleanup": "No stack was started. node_modules removed in the cloud worktree (23 directories, 0 left). git worktree remove /home/user/rig-os24430c/cloud without --force: exit 0. The framework clone and the turbo cache were removed with rm, and /home/user/rig-os24430c is gone. The pin trees be076dad and ec749fbb are untouched. The shared checkouts were never edited, stashed or checked out: /home/user/cloud had fetches only, and git status there reads 0 entries."
    }

    Generated by Claude Code

  13. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → cloud#3070 comment 6261588592 · repo:cloud#2 · session session_01Cbdek8SKSHxddGqTPZXdqX · 2026-10-19T07:00Z. Ready and auto-merge in this act (head 1f7e76ed): PR 2, the admin-grant-organization backfill step. With it merged, the seat posts the hand-off on objectstack#15212 and closes this card.

  14. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Closed completed · repo:cloud#2 (seat post #24145, session session_01Cbdek8SKSHxddGqTPZXdqX) · 2026-10-19T07:19Z · both PRs merged, hand-off posted

    acceptance outcome where
    1 · a fresh hosted environment, at a pin carrying 8b-0a, has the owner's admin_full_access owned by the Default Organization and resolves standing through it met, measured in both shapes cloud#3063 (4a5c8d26), pin 584b4225
    2 · the same for an environment provisioned before, after cloud's upgrade path met: the admin-grant-organization backfill step, verified per database; 0 or 2+ organizations stay unverified and named cloud#3070 (5ad201fc)
    3 · the writer reports its failure met: five failure reasons returned and logged at error level; sign-in and boot still succeed cloud#3063
    hand-off on #15212 posted (6261871584) —

    Ruling B (6258957472), confirmed by the maintainer (「24430 同意。」 6259794416).

    Carried elsewhere:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardspriority:p1High: required for production / M2repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)target:v18

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions