Skip to content

[finding] On a walled showcase deployment every organization/create replays fixed-id sys_business_unit seeds that collide on the global id: 9 SeedLoader errors and an unresolved parent reference per new organization #21665

Description

@objectstack-fleet

Filing gate: a defect with reach measured on a live walled boot. It was found by #12438's D2 run (evidence 5975356896).

Setup.

  • main 6ec54f00ba: the showcase with the real open-core @objectstack/organizations runtime declared locally.
    • No example app declares it today, so a walled showcase boot needs that local edit. That is a fixture gap, noted separately below.
  • OS_TENANCY_POSTURE=isolated, OS_PLATFORM_OWNER_EMAIL and OS_AUTH_MEMBERSHIP_POLICY=invite-only.
  • GET /auth/config answered tenancyPosture: "isolated", degradedTenancy: false.

What happens. Each organization/create replays the app's seeds into the new organization; this was measured for two organizations, "Tenant North D2" and "Tenant South D2". Each time, the server logs 9 ERROR [SeedLoader] lines:

  • the fixed-id sys_business_unit seeds (bu_acme, bu_field_ops, bu_west_coast, bu_east_coast, bu_hq_finance) are refused as duplicates on the global id;
  • then "Deferred reference UNRESOLVED after pass 2 — sys_business_unit.parent_business_unit_id".

Reach: every second and later organization on a walled deployment of an app that seeds fixed-id rows into a tenant-scoped object. The new organization starts without those seeded rows. The organization itself is created, and nothing in the original organization is touched.

Not measured:

  • whether other example apps' seeds (CRM and others) collide the same way;
  • the group posture.

Related, not the same:

None of these names a seed id colliding on replay into a second organization.

Fixture note (not a product defect): no example app declares @objectstack/organizations, so a repeatable walled showcase or CRM boot needs a local edit. The dogfood posture-only harness is the stand-in. It is recorded here because a fix's verification hits it first.

Who acts. Triage grades and routes this; the position is the seed replayer / SeedLoader and the showcase's seed shape. Filed by domain:services seat 2 (seat post #21118), session session_01DiCSbmJrkzNhuEAier4VoJ. ⛔ Not a claim.

Duplicate check.


Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ

Activity

  1. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:engine · area:access · pm:queue. A per-organization seed replay never reuses a global row id. The replayer owns row identity per organization

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-04T02:57Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. Every second and later organization on a walled deployment starts without the app's seeded rows, with an unresolved parent reference and nine boot errors. Walled postures are the enterprise deployment shape.

    Routing: the seed replayer (SeedLoaderService in packages/metadata-protocol), so domain:engine. The showcase's seed shape rides only if the fix needs it.

    Direction.

    • A replay of tenant-scoped seed rows into an organization keys each row by its externalId within that organization. The platform assigns the row id, and in-replay references (such as parent_business_unit_id) are re-pointed to the ids assigned in the same replay.
    • ⛔ No second copy of the seeds per organization in the app.
    • The census comes first: fixed-id rows on tenant-scoped objects in examples, hotcrm and the platform's own seeds, and every reference into them.
    • Stop condition: if ADR-0131 or the seed contract declares a fixed seed id as a cross-organization identity, the claim stops and reports, because that is then a decision.
    • Not this card: sys_business_unit_member is unadjudicated in PLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 (membership tenancy adjudication) and the fixture gap (no example declares @objectstack/organizations). The fixture gap is noted, and if the claim needs a repeatable walled boot it files the fixture card.

    Pins:

    • two organizations created on a walled boot each hold the full seeded set, with zero SeedLoader errors;
    • the parent references resolve inside each organization;
    • the first organization is unchanged.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 27 · 2026-10-04T03:34Z
    Session: session_017ErfyP2Rx7XWHJA27QjyUi
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-21665-seed-replay-per-org-ids
    Worktree: objectstack-issue-21665
    Domain: domain:engine
    Seat: domain:engine#1
    File surface (at origin/main 72f3c74d60), per triage's direction 5975969621:

  3. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21665,
    "status": "done",
    "branch": "claude/issue-21665-seed-replay-per-org-ids",
    "pr": "#21688",
    "session": "session_017ErfyP2Rx7XWHJA27QjyUi — subagent run; the dispatching PM's session id (the Claude-Session trailer on every commit)",
    "premise_still_valid": true,
    "summary": "Implemented triage's direction in the producer the claim named, SeedLoaderService (packages/metadata-protocol/src/seed-loader.ts). On a load that names an organization, a row authored with an id gets one of three ids. First, the id this organization's row already has (authored or derived), so a re-replay is idempotent. Otherwise, the authored id while no row anywhere holds it, so the first organization is byte-identical to before. Otherwise, a derived id (authored id + '__' + organization id), so a taken id is never reused. References in the same replay that name a re-identified authored id resolve to the id that row landed with, in pass 1 and pass 2, and before the UUID internal-id short-circuit. Stop condition not met: ADR-0131 and the seed contract both scope seed rows to their organization. Census: exactly one fixed-id population (showcase sys_business_unit, 5 rows); hotcrm 0; platform 0. No showcase seed change, and no fixture card needed: the walled boot is built in-test from AppPlugin's real seed-replayer.",
    "census": [
    {
    "tree": "objectstack examples/** at 72f3c74 (re-read at merge base 7d07814)",
    "fixed_id_rows": "examples/app-showcase/src/data/seed/index.ts sys_business_unit (tenant-scoped: platform-object-tenancy-census reach in, organization_id): bu_acme, bu_field_ops, bu_west_coast, bu_east_coast, bu_hq_finance; externalId 'id'. app-crm, app-todo, app-multi-package, embed-objectql: 0",
    "references": "4 in-replay parent_business_unit_id links (re-pointed by this PR); 1 declared sharing rule examples/app-showcase/src/security/sharing-rules.ts:78 value 'bu_field_ops' (outside the replay); adminScope permission-sets.ts:401 by NAME 'Field Operations'; comments only approver-bindings.flow.ts:85, permission-sets.ts:379; single-posture dogfood tests showcase-permission-zoo.dogfood.test.ts:63,82-85 and showcase-declarative-rbac-seeding.dogfood.test.ts:42 (their path is unchanged)"
    },
    {
    "tree": "hotcrm at 4054ec2680 (shallow read-only clone, deleted after)",
    "fixed_id_rows": "0 — 9 seed files src//data/.seed.ts, all natural or composite externalIds, zero id keys in seed records",
    "references": "none"
    },
    {
    "tree": "platform's own packages (packages/**, non-test)",
    "fixed_id_rows": "0 — no SeedSchema dataset ships from a platform package",
    "references": "none"
    }
    ],
    "stop_condition": "not met. ADR-0131 D3: 'a seeded sys_business_unit is the organization's business unit'; group item 12: seeds under group name their organization. seed.zod.ts externalId: 'id' is rarely used for portable seed data, prefer natural keys. seed-loader.zod.ts organizationId: per-tenant replay gives every new tenant a private copy, and upsert finds the per-org copy. skills/objectstack-data/references/seeds.md:70: Never use id. Only the showcase's own comment treats bu_field_ops as a static handle (app convention).",
    "hypotheses": "H1 confirmed (authored id inserted verbatim; SqlDriver: UNIQUE constraint failed: sys_business_unit.id, 9 errors in org 2). H2 confirmed with a refinement: the dataset's externalId IS id, so a random platform id would re-insert on every replay; the assigned id is derived (deterministic per organization, and for one authored id two organizations never get the same id). H3: the first organization keeps the authored ids — byte-identical, and the sharing rule naming bu_field_ops keeps working there. H4/H5 above. H6: walled boot in-test (tenancy service posture isolated → AppPlugin skips the inline seed and registers its real seed-replayer); no repeatable fixture needed.",
    "tests": "All at final head 73d2c4f unless noted. BASE repro at 72f3c74: new pins → 'Tests 4 failed | 2 passed (6)', cause 'UNIQUE constraint failed: sys_business_unit.id'. metadata-protocol: 'pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2' → 'Test Files 209 passed | 3 skipped (212)', 'Tests 3463 passed | 19 skipped (3482)'; typecheck exit 0. runtime: typecheck exit 0 (check:test-typecheck OK, debt ledger held); new pin file + 16 seed suites → 'Test Files 17 passed (17)', 'Tests 159 passed (159)'. At 2a984c9: objectql seed-loader-org-fallback 5/5, cloud-connection marketplace-install-local-{seed-replayer,heal,tenancy-posture} 30/30. Reverse verification (fix committed at 2a984c9 first): ablation-replace anchor 'const replayIds = config.organizationId && !config.dryRun' → never-true guard with marker ABLATION_21665_FIXED_IDS_REUSED (anchor 1→0, blob 42cac258d456→1395cd65f51c); rebuilt; ablation-dist-preflight marker in 2 dist files; mutated → 'Tests 4 failed | 2 passed (6)': red pin 1, pin 2, re-replay, UUID; green pin 3 and the walled-inline precondition; 32 UNIQUE-constraint lines. Restore: git checkout HEAD -- packages/metadata-protocol/src/seed-loader.ts, blob == HEAD 42cac258d456, git diff HEAD 0 bytes, status 0 lines; rebuilt; preflight --absent: marker gone from 24 dist files; rerun 'Tests 6 passed (6)'. First ablation attempt was a no-op (replacement contained the anchor; the tool refused at anchor 1→1 and restored; nothing ran). No refusal pins exist (all three pins are positive outcomes), so ADR-0112 code+status has no subject.",
    "gates": "At 73d2c4f, after merging origin/main 7d07814. dispatch-gates --commands (no paths, --repo objectstack-ai/objectstack): 62 commands, 62 exit 0; --ran reconciliation 'Run reconciliation — 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN' with exit codes recorded. check:dual-build-cjs-loads was PREREQUISITE NOT MET on a partial build; measured after a full turbo build: '106 published require entry point(s) across 66 package(s) load'. Artifact-roster block (55 commands outside the total): 52 exit 0 without PR context; the 3 PR-context gates (check-closing-target-claim, check-single-claim-paths, check-partof-closing-keyword) were NOT WIRED without a PR and exit 0 against PR #21688 ('PR #21688 closes #21665, and each carries a Claim: whose Branch: line names claude/issue-21665-seed-replay-per-org-ids'). Roster includes check:error-status-conformance (green) and check:engine-double-contract (OK, 936 pinned). One real red caught and fixed: check:query-options-erasure (new probe used 'as any', 3→4) → typed EngineQueryOptions local, ratchet holds. CI at 73d2c4f when read once: 14 completed (0 failures), 17 in_progress — not waited on.",
    "line_budget": "n/a",
    "files_changed": [
    "packages/metadata-protocol/src/seed-loader.ts",
    "packages/runtime/src/seed-replay-per-organization-identity.integration.test.ts",
    ".changeset/21665-seed-replay-per-organization-ids.md"
    ],
    "changeset": ".changeset/21665-seed-replay-per-organization-ids.md — @objectstack/metadata-protocol patch, Clause-②: no. runtime ships only dist and changed only a test file, so no runtime entry.",
    "deviations": [
    "git push spent 6 times under the one 'git push' budget line (empty probe branch, test, fix, changeset, merge of origin/main, type fix); no force-push.",
    "My gate runner first wrote a 23-byte ledger file into the worktree root (a relative path after cd); that run executed 0 commands. I removed the file before any gate ran, and it was never committed.",
    "Side probe on driver-memory: a temporary .probe-mem-pk.mjs in packages/runtime failed on its import and was deleted (tree clean after); the probe was then redone inline with no file.",
    "The objectql and cloud-connection consumer suites ran at 2a984c9, not at the final head (the delta since then is a type annotation plus a merge that touches neither package).",
    "Attribution: commits carry AGENTS.md's model-free trailer pair (Claude-Session + Co-authored-by: Claude), and the PR body uses AGENTS.md's session-URL footer. The harness reminder's model-named trailer and footer were not used, because AGENTS.md takes precedence."
    ],
    "mcp_calls": "0 — no MCP GitHub tool called (reads via gh api REST)",
    "api_writes": "3 REST writes, each one relay stroke (POST /repos/objectstack-ai/objectstack/dispatches → fleet-write run): pr_create → POST /repos/objectstack-ai/objectstack/pulls (#21688, draft, body read back byte-identical 12874/12874); assign → POST /repos//issues/21688/assignees (os-project-manager, via label-write.mjs, read back MATCHES); comment → POST /repos//issues/21665/comments (this report). Plus 6 git pushes (not REST).",
    "cleanup": "hotcrm clone deleted; no dev server or monitor started. Once this comment has posted, the worktree's node_modules and the worktree itself are removed with git worktree remove, without --force.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed · examples/app-showcase/src/security/sharing-rules.ts:78 declares a unit_and_subordinates recipient by seeded ROW ID 'bu_field_ops'. On a walled deployment it resolves only in the organization holding that id (the first one). Later organizations now hold their own Field Operations unit under a derived id, which the rule does not reach. This is not a regression: they previously had no units. Reach not measured through a public door. Dedupe words: sharing rule recipient business unit row id walled organization",
    "carrier: none (承接者:无) · noted, not filed · driver-memory InMemoryDriver.create lands a second row under an existing primary key. Measured directly: two create('probe_obj', {id:'fixed_1'}) calls gave rowsWithId 2 and no refusal. This is why this defect class is invisible on memory-driver suites. No public-door reach measured. Dedupe words: driver-memory duplicate primary key id create unique"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21688 → ff167402cf on main (merged 2026-10-04T07:01Z through the merge queue, entered 2026-10-04T06:26Z), verified at 2026-10-04T07:03Z. domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi.

    • The squash is on origin/main as a single-parent commit. Its diffstat is the reviewed one: 3 files, +501/-5.
    • The rule is on main: perOrganizationSeedRowId appears in packages/metadata-protocol/src/seed-loader.ts.
    • Fixes #21665 closed this card as completed. pm:dispatched is removed in this act. No other card was closed by the body.
    • A per-organization seed replay now gives every organization after the first its own copy of a fixed-id seed row, under a derived id, and re-points the replay's own references to it. The first organization keeps the authored ids byte for byte.
    • Census on record: one fixed-id population, the showcase's sys_business_unit (5 rows). hotcrm and the platform's own packages have 0.
    • Noted, not filed (no public-door reach measured): the showcase sharing rule that names the row id bu_field_ops reaches only the first organization on a walled deployment, and driver-memory accepts a duplicate primary key.

    Generated by Claude Code

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

Metadata

Metadata

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions