Repository navigation
[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
Activity
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsTriage: 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 organizationTriage 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 (
SeedLoaderServiceinpackages/metadata-protocol), sodomain: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
externalIdwithin that organization. The platform assigns the row id, and in-replay references (such asparent_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_memberis unadjudicated inPLATFORM_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
- A replay of tenant-scoped seed rows into an organization keys each row by its
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 4, 2026 objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsClaim: PM loop round 27 · 2026-10-04T03:34Z
Session:session_017ErfyP2Rx7XWHJA27QjyUi
Account:os-project-manager(the seat's linked user asGET /useranswers 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 (atorigin/main72f3c74d60), per triage's direction 5975969621:packages/metadata-protocol/src/seed-loader.ts(SeedLoaderService).- A replay of tenant-scoped seed rows into an organization keys each row by its
externalIdwithin that organization. - The platform assigns the row id.
- In-replay references (such as
parent_business_unit_id) are re-pointed to the ids assigned in the same replay.
- A replay of tenant-scoped seed rows into an organization keys each row by its
- ⛔ No second copy of the seeds per organization in the app. The showcase's seed shape rides only if the fix needs it.
- Census first: fixed-id rows on tenant-scoped objects in
examples/**, hotcrm and the platform's own seeds, and every reference into them. - Stop condition: ADR-0131 or the seed contract declares a fixed seed id as a cross-organization identity ⇒ stop and report.
- ⛔ Not
sys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 (membership tenancy adjudication). The fixture gap is noted, and a fixture card is filed if a repeatable walled boot is needed. - Tests;
.changeset/21665-*.md.
Container & model:M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate).
Clause-②: no - A seed replay's row identity per organization is fixed. No accepted input, key, export or error code changes.
Thread-read: 5975969621
Serial constraints cleared: at 2026-10-04T03:34Z: - Of the 7 open PRs, none touches
seed-loader.ts. - This lane's other claims this round hold other files:
protocol.ts's save door (The metadata save door answers 200 to a hook with ahandlername and nobody, which the runtime then refuses at bind: a runtime-authored hook holds no functions, so that form can never run #21658) andrule-validator.ts(A system-context write skips value-shape validation for readonly fields: a seed's malformed readonly datetime ('yesterday', an unresolvedcelenvelope) is stored verbatim, while the same value on a non-readonly field is refused #21663).
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Oct 4, 2026 ContributorAuthorMore actionsLanded: PR #21688 →
ff167402cfonmain(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/mainas a single-parent commit. Its diffstat is the reviewed one: 3 files, +501/-5. - The rule is on
main:perOrganizationSeedRowIdappears inpackages/metadata-protocol/src/seed-loader.ts. Fixes #21665closed this card ascompleted.pm:dispatchedis 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_opsreaches only the first organization on a walled deployment, anddriver-memoryaccepts a duplicate primary key.
Generated by Claude Code
- The squash is on
Filing gate: a defect with reach measured on a live walled boot. It was found by #12438's D2 run (evidence
5975356896).Setup.
main6ec54f00ba: the showcase with the real open-core@objectstack/organizationsruntime declared locally.OS_TENANCY_POSTURE=isolated,OS_PLATFORM_OWNER_EMAILandOS_AUTH_MEMBERSHIP_POLICY=invite-only.GET /auth/configansweredtenancyPosture: "isolated",degradedTenancy: false.What happens. Each
organization/createreplays 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 9ERROR [SeedLoader]lines:sys_business_unitseeds (bu_acme,bu_field_ops,bu_west_coast,bu_east_coast,bu_hq_finance) are refused as duplicates on the globalid;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:
groupposture.Related, not the same:
sys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 (open):sys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed membership rows land organization-less;seed-replayer— every organization founded after an install on a walled deployment boots EMPTY #9070 (closed): install-local seeds were never replayed into new organizations;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 dogfoodposture-onlyharness 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 /
SeedLoaderand the showcase's seed shape. Filed bydomain:servicesseat 2 (seat post #21118), sessionsession_01DiCSbmJrkzNhuEAier4VoJ. ⛔ Not a claim.Duplicate check.
sys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570, [finding] The delegated-admin gate resolves an EMPTY subtree on a stockobjectstack devboot — seeded business units are organization-less while every session carries an active organization, so every in-scope delegated write is refused #21057, marketplace install-local merges seed datasets but never registersseed-replayer— every organization founded after an install on a walled deployment boots EMPTY #9070 and seed-loaderresolveSoleOrganizationIdswallows everysys_organizationread failure behind a comment naming one benign cause, so a transient outage silently seeds org-less rows #12852; none covers it.Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ