You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
seed: a seed write carries no organization in its execution context, so a system hook that pins the writer's organization computes nothing for seeded rows #33206
Found while converting a downstream app's demo book to standard seeds on the published 18.4.0 packages (downstream card: steedos-labs/os-project-blackcat-crm#55, its order half is held on this). Present on main at 64551047.
Symptom
A seeded order row lands, organization-stamped, but the app's system hooks that derive data from it compute nothing: the plan-recommendation hook creates an empty "recommended" plan, and every plan's margin and verdict stay null. The same order created through the API by a user computes correctly (control).
Producer
packages/spec/src/kernel/execution-context.zod.ts:735 — SEED_WRITE_EXECUTION_CONTEXT = { isSystem: true, skipTriggers: true, seedReplay: true }: no organizationId, although the loader knows the organization it loads for (SeedLoaderConfig.organizationId) and stamps it on every row it writes.
A hook that runs runAs: 'system' and pins its reads/writes to the caller's organization (the multi-tenant rule apps are taught: a system hook scopes to ctx's organization, never across orgs) therefore reads nothing in the seed context: no plants, costs or freight rows are visible to it, so it derives nothing.
Why it matters
Any app whose sample data includes rows that system hooks enrich (recommendations, roll-ups, derived verdicts) cannot ship that data as a standard seed. It has to stay in an app-side script that writes through the API, which is exactly what a seed is meant to replace, and such a script does not run on a hosted install with sample data.
Suggested fix shape (for the lane to judge)
The seed loader passes the organization it is loading for into the write context ({ ...SEED_WRITE_EXECUTION_CONTEXT, organizationId }) on every org-scoped load (boot under single, per-organization replay, install, reseed), so a system hook sees the same organization the row is stamped with. A load with no organization keeps today's context.
Downstream evidence (steedos-labs/os-project-blackcat-crm PR #59 report): seeded crm_cb_order rows → recommended plan plan_kind: manual with no lines, every plan margin: null, verdict: null; the same orders placed through the API → 11/11 plans match the workbook.
Dedupe: is:issue SEED_WRITE_EXECUTION_CONTEXT (10 hits, none on the organization; nearest #21646, created_at overwritten in the seed context). New. Dedupe words: seed execution context organization · seedReplay organizationId hook · system hook org pin seed.
Row: a seed write carries no organizationId, so a system hook that scopes to the writer's organization reads nothing and derives nothing for seeded rows: an empty recommended plan, and margins and verdicts null. That is wrong-running (p2).
Raise (+1 → p1), a real user can hit it: a downstream app on published 18.4.0 hit it, and its card is held on this one. The multi-tenant hook rule apps are taught produces it.
Claim: PM loop round 17, card thread · 2026-11-14T04:04Z · p1 per triage 6870461000 (a downstream app on published 18.4.0 hit it); one card thread with #33207, same seed loader
Session: session_01L9qibzSAkofrHDU1U8yFeA
Account: os-tesla
Branch: claude/project-thread-e226dr
Worktree: objectstack-issue-33207
Domain: domain:server
Seat: domain:server#7
File surface: packages/metadata-protocol/src/seed-loader.ts and the replayer or install path that builds the seed write context (named in the shape note before code); packages/spec/src/kernel/execution-context.zod.ts only if the shape note names it and the seat accepts it; their tests; the seed docs page that states the reference rule or the seed context; one root changeset. Stop on breach.
Container & model: L, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; PM default), effort: high
Clause-②: yes (widening) — references that never resolved on an org-scoped load resolve, and system hooks see the load's own organization; any narrowing is named in the shape note before code
Metadata-protocol: none — unless the change lands in an authorable spec schema, stated before code
Responsibility: the seed loader's reference probe and seed write context (metadata-protocol, seed-loader.ts), objectstack | a downstream app shipping sample data as standard seeds | every org-scoped seed load (per-organization replay, install, reseed, boot under single)
Thread-read: 6870461000
Premise: re-measure at origin/main by a run first — the unresolved sys_user reference with its swallowed refusal, and the seed write context a system hook sees, each against its control
Serial constraints cleared: this seat's #31699 works in metadata-protocol migrations and plugin.ts, not the seed loader; no open PR names seed-loader.ts at claim time
Claim amendment to this seat's claim 6871417580 on #33206 · 2026-11-14T04:13Z: the seat accepts the shape note out/33207-33206-proposed-shape.md (measured at origin/main6e1ab9b), one PR for #33207 and #33206, with these choices.
The seed write context carries the load's organization as tenantId, composed at the loader's call site: { ...SEED_WRITE_EXECUTION_CONTEXT, tenantId: org }. ExecutionContext has no organizationId key, and hooks read the organization from tenantId. SEED_WRITE_EXECUTION_CONTEXT is not changed, so packages/spec is untouched and Metadata-protocol: none stands.
The organization is the one the load stamps rows with, config.organizationId ?? fallbackOrgId, so the boot under single is covered. A load with neither a pinned nor a derived organization keeps today's context byte for byte.
Reference probes: a target that carries organization_id keeps today's filter. sys_user resolves only to a member of the load's organization, through sys_member. sys_organization resolves only to the load's own organization. An app-owned global object is probed without the filter. A platform-managed tenant-less table stays refused. A refused probe is reported once per target, field and code; it never refuses the load.
Clause-②: yes (narrowing). Under isolated / group, a hook's read made from a seed write on an org-scoped load returns the load organization's rows instead of every organization's. The widened members are named in prose: references that now resolve, and hooks that now see the load's organization. The contract review runs at CONTRACT_REVIEW_TIER, and the final head gets a full dogfood run.
The file surface becomes:
packages/metadata-protocol/src/seed-loader.ts;
a new packages/runtime/src/seed-org-scoped-references.integration.test.ts, and existing seed-loader unit pins only where they break;
content/docs/data-modeling/seed-data.mdx;
.changeset/33207-seed-org-scoped-references.md.
packages/spec, app-plugin.ts and cloud-connection are not touched.
{
"issue": 33206,
"status": "done",
"branch": "claude/project-thread-e226dr",
"pr": "https://github.com/objectstack-ai/objectstack/pull/33222",
"session": "session_01L9qibzSAkofrHDU1U8yFeA",
"premise_still_valid": true,
"summary": "One draft PR closes #33207 and #33206 (Fixes both). Reference probes are judged per target on an organization's load: tenant-carrying or undescribable targets keep the organization filter; sys_user resolves only through a sys_member row in the load organization (a non-member is never used); sys_organization answers only for the load's own; author-declared global objects are unscoped; other platform-managed tenant-less tables keep the old probe. A refused probe is recorded, said once per target/field/code and named in the pass-2 error; the load is never refused for it. Every seed write of a load carries tenantId = config.organizationId ?? fallbackOrgId (the card's organizationId key reaches no hook: ExecutionContext has none); SEED_WRITE_EXECUTION_CONTEXT unchanged; a load with no organization uses the frozen SEED_OPTIONS bag. Clause-② yes (narrowing): under isolated/group a hook's ctx.api read on a seeded row is now scoped to the load organization. Owes a contract review at CONTRACT_REVIEW_TIER from the new review agent.",
"tests": "New packages/runtime/src/seed-org-scoped-references.integration.test.ts (real AppPlugin replayer and single inline seed, SeedLoaderService, ObjectQL, SqlDriver): 12/12 at 3c36f9c6e. Six ablations via ablation-replace.mjs + OS_SKIP_DTS=1 build + ablation-dist-preflight (marker present in 2 built files): A membership ignored -> non-member pin red; B tenant filter dropped -> CONTROL tenant + refused-probe pins red; C bare catch -> refused-probe pin red; D own-org filter dropped -> org-table pin red; E write context without org -> 4 hook pins red; F sys_user probed with org filter (main) -> member + non-member pins red. Each restored (blob == HEAD 337629ad067a, git diff HEAD empty); restore leg rebuilt, all markers --absent, 12/12 green. metadata-protocol vitest: 338 files passed/7 skipped, 22810 tests passed/36 skipped (pre-merge). After merging origin/main: runtime seed files (4) 29/29.",
"gates": "dispatch-gates --commands (94 lines, stdin closed, exit codes on disk): 90 exit 0 first run; check:query-options-erasure 1 -> two new probe bags typed EngineQueryOptions -> 0; check:test-source-alias 1 -> module-top import of @objectstack/metadata in the pins -> 0; check:skill-examples 3 (spec declarations missing) -> 0 after dts build; check-plugin-teardown-shape --self-test 3 (fixture commit outside the shallow clone) -> 0 after fetching it; check:dual-build-cjs-loads 3 PREREQUISITE NOT MET (needs every dist/, a full pnpm build): NOT MEASURED, CI Lint & Repo Gates runs it. pnpm lint: exit 0. metadata-protocol typecheck + runtime typecheck (incl. check:test-typecheck): exit 0 after a declaration build. CI check-runs: in_progress at report time.",
"dogfood_battery": "3c36f9c6e · VERDICT command-exit 1 · Test Files 1 failed | 463 passed | 3 skipped (467) · Tests 6 failed | 3921 passed | 42 skipped (3969) · the red file test/rest-route-generation-tombstones.test.ts failed on TS7016 (my post-merge rebuild skipped spec declarations); rerun after a spec declaration build: 1/1 files, 10/10 tests passed. No failure reaches the diff.",
"mcp_calls": "8 — reads only: issue_read x4 (both cards and comments), list_pull_requests x1, pull_request_read get_files x3; 0 writes",
"api_writes": "3 — relay pr_create (#33222, assignee os-tesla); post-stamped comment on #33207; post-stamped comment on #33206",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none · noted, not filed: AppPlugin's no-metadata basic-insert fallback writes seeds with SEED_WRITE_OPTIONS and no organization; it stamps nothing either, so it was left as is (Acceptance notes).",
"carrier: none · noted, not filed: platform-managed tables with no organization column other than sys_user/sys_organization keep a refused probe on an organization's load; now reported once, no scope rule invented."
]
}
One PR for both cards (#33207 and #33206): #33222, head 3c36f9c6e. It owes a contract review at CONTRACT_REVIEW_TIER (narrowing) from the new review agent before it can land.
Release: session_01L9qibzuZJBQY4wUQFJbkfx · seat domain:server#7 signs off on the maintainer's word 「所有座位当前任务处理完,合并后就下班」 (2026-11-14T04:35Z, project chat) and 「各座位自己的契约复审也暂停,等新的 review agent 上线后复审」 (2026-11-14T04:37Z) · back to pm:queue for the next claimant · 2026-11-14T06:11Z
Resume-from: claude/project-thread-e226dr@3c36f9c6edfb7f70ef9143b0e65bc1fb036d49d8
The shape is claim amendment 6871533047: tenantId is composed at the call site, the spec constant is untouched, and the organization is config.organizationId ?? fallbackOrgId.
Claim: resume after the 04:35Z sign-off, card thread · 2026-11-14T15:22Z · the seat is back on duty for review and landing only (「你现在可以自己契约复审了。」 2026-11-14T14:17Z; 「车道座位应该使用pm-dispatch 技能审核和落地pr」 2026-11-14T14:32Z); one card thread with #33207, PR #33222
Session: session_01L9qibzSAkofrHDU1U8yFeA
Account: os-tesla
Branch: claude/project-thread-e226dr
Worktree: objectstack-issue-33207
Domain: domain:server
Seat: domain:server#7
File surface: unchanged — claim 6871417580 with amendment 6871533047; PR #33222 touches seed-loader.ts, its two tests, the seed docs page and one changeset. Stop on breach.
Container & model: L, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; PM default), effort: high
Clause-②: yes (narrowing), as declared on PR #33222 and reviewed at CONTRACT_REVIEW_TIER
Metadata-protocol: none
Responsibility: the seed loader's reference probe and seed write context (metadata-protocol, seed-loader.ts), objectstack | a downstream app shipping sample data as standard seeds | every org-scoped seed load
Thread-read: 6879156542
Premise: re-measured by the card thread on 41de6f5 (records 6878779800, 6879099931, 6879156542)
Serial constraints cleared: no other open PR of this seat names seed-loader.ts
Activity
objectstack-fleet commentedon Nov 14, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: first touch →
domain:server· p1 ·pm:queue· 2026-11-14T02:47Z⛔ Not a claim.
organizationId, so a system hook that scopes to the writer's organization reads nothing and derives nothing for seeded rows: an empty recommended plan, and margins and verdictsnull. That is wrong-running (p2).SEED_WRITE_EXECUTION_CONTEXT(spec) plus the seed loader passing the organization it already knows. Take it in the same session as seed: on an organization-scoped load a reference to sys_user (owner_id: NAME) never resolves — the reference probe adds organization_id to a tenant-less object and swallows the refusal #33207 (same loader).objectstack-fleet commentedon Nov 14, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Claim: PM loop round 17, card thread · 2026-11-14T04:04Z · p1 per triage 6870461000 (a downstream app on published 18.4.0 hit it); one card thread with #33207, same seed loader
Session:
session_01L9qibzSAkofrHDU1U8yFeAAccount:
os-teslaBranch:
claude/project-thread-e226drWorktree:
objectstack-issue-33207Domain:
domain:serverSeat:
domain:server#7File surface:
packages/metadata-protocol/src/seed-loader.tsand the replayer or install path that builds the seed write context (named in the shape note before code);packages/spec/src/kernel/execution-context.zod.tsonly if the shape note names it and the seat accepts it; their tests; the seed docs page that states the reference rule or the seed context; one root changeset. Stop on breach.Container & model:
L,mode:thread,model: default (dispatch-gates --tier: no path-derived mandate; PM default),effort: highClause-②: yes (widening) — references that never resolved on an org-scoped load resolve, and system hooks see the load's own organization; any narrowing is named in the shape note before code
Metadata-protocol: none — unless the change lands in an authorable spec schema, stated before code
Responsibility: the seed loader's reference probe and seed write context (
metadata-protocol,seed-loader.ts), objectstack | a downstream app shipping sample data as standard seeds | every org-scoped seed load (per-organization replay, install, reseed, boot undersingle)Thread-read: 6870461000
Premise: re-measure at
origin/mainby a run first — the unresolvedsys_userreference with its swallowed refusal, and the seed write context a system hook sees, each against its controlSerial constraints cleared: this seat's #31699 works in
metadata-protocolmigrations andplugin.ts, not the seed loader; no open PR namesseed-loader.tsat claim timeobjectstack-fleet commentedon Nov 14, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Claim amendment to this seat's claim 6871417580 on #33206 · 2026-11-14T04:13Z: the seat accepts the shape note
out/33207-33206-proposed-shape.md(measured atorigin/main6e1ab9b), one PR for #33207 and #33206, with these choices.tenantId, composed at the loader's call site:{ ...SEED_WRITE_EXECUTION_CONTEXT, tenantId: org }.ExecutionContexthas noorganizationIdkey, and hooks read the organization fromtenantId.SEED_WRITE_EXECUTION_CONTEXTis not changed, sopackages/specis untouched andMetadata-protocol: nonestands.config.organizationId ?? fallbackOrgId, so the boot undersingleis covered. A load with neither a pinned nor a derived organization keeps today's context byte for byte.organization_idkeeps today's filter.sys_userresolves only to a member of the load's organization, throughsys_member.sys_organizationresolves only to the load's own organization. An app-owned global object is probed without the filter. A platform-managed tenant-less table stays refused. A refused probe is reported once per target, field and code; it never refuses the load.Clause-②: yes (narrowing). Underisolated/group, a hook's read made from a seed write on an org-scoped load returns the load organization's rows instead of every organization's. The widened members are named in prose: references that now resolve, and hooks that now see the load's organization. The contract review runs atCONTRACT_REVIEW_TIER, and the final head gets a full dogfood run.The file surface becomes:
packages/metadata-protocol/src/seed-loader.ts;packages/runtime/src/seed-org-scoped-references.integration.test.ts, and existingseed-loaderunit pins only where they break;content/docs/data-modeling/seed-data.mdx;.changeset/33207-seed-org-scoped-references.md.packages/spec,app-plugin.tsand cloud-connection are not touched.objectstack-fleet commentedon Nov 14, 2026
domain:server#7 · os-tesla · session_01L9qibzSAkofrHDU1U8yFeA
os-dev-report
{ "issue": 33206, "status": "done", "branch": "claude/project-thread-e226dr", "pr": "https://github.com/objectstack-ai/objectstack/pull/33222", "session": "session_01L9qibzSAkofrHDU1U8yFeA", "premise_still_valid": true, "summary": "One draft PR closes #33207 and #33206 (Fixes both). Reference probes are judged per target on an organization's load: tenant-carrying or undescribable targets keep the organization filter; sys_user resolves only through a sys_member row in the load organization (a non-member is never used); sys_organization answers only for the load's own; author-declared global objects are unscoped; other platform-managed tenant-less tables keep the old probe. A refused probe is recorded, said once per target/field/code and named in the pass-2 error; the load is never refused for it. Every seed write of a load carries tenantId = config.organizationId ?? fallbackOrgId (the card's organizationId key reaches no hook: ExecutionContext has none); SEED_WRITE_EXECUTION_CONTEXT unchanged; a load with no organization uses the frozen SEED_OPTIONS bag. Clause-② yes (narrowing): under isolated/group a hook's ctx.api read on a seeded row is now scoped to the load organization. Owes a contract review at CONTRACT_REVIEW_TIER from the new review agent.", "tests": "New packages/runtime/src/seed-org-scoped-references.integration.test.ts (real AppPlugin replayer and single inline seed, SeedLoaderService, ObjectQL, SqlDriver): 12/12 at 3c36f9c6e. Six ablations via ablation-replace.mjs + OS_SKIP_DTS=1 build + ablation-dist-preflight (marker present in 2 built files): A membership ignored -> non-member pin red; B tenant filter dropped -> CONTROL tenant + refused-probe pins red; C bare catch -> refused-probe pin red; D own-org filter dropped -> org-table pin red; E write context without org -> 4 hook pins red; F sys_user probed with org filter (main) -> member + non-member pins red. Each restored (blob == HEAD 337629ad067a, git diff HEAD empty); restore leg rebuilt, all markers --absent, 12/12 green. metadata-protocol vitest: 338 files passed/7 skipped, 22810 tests passed/36 skipped (pre-merge). After merging origin/main: runtime seed files (4) 29/29.", "gates": "dispatch-gates --commands (94 lines, stdin closed, exit codes on disk): 90 exit 0 first run; check:query-options-erasure 1 -> two new probe bags typed EngineQueryOptions -> 0; check:test-source-alias 1 -> module-top import of @objectstack/metadata in the pins -> 0; check:skill-examples 3 (spec declarations missing) -> 0 after dts build; check-plugin-teardown-shape --self-test 3 (fixture commit outside the shallow clone) -> 0 after fetching it; check:dual-build-cjs-loads 3 PREREQUISITE NOT MET (needs every dist/, a full pnpm build): NOT MEASURED, CI Lint & Repo Gates runs it. pnpm lint: exit 0. metadata-protocol typecheck + runtime typecheck (incl. check:test-typecheck): exit 0 after a declaration build. CI check-runs: in_progress at report time.", "dogfood_battery": "3c36f9c6e · VERDICT command-exit 1 · Test Files 1 failed | 463 passed | 3 skipped (467) · Tests 6 failed | 3921 passed | 42 skipped (3969) · the red file test/rest-route-generation-tombstones.test.ts failed on TS7016 (my post-merge rebuild skipped spec declarations); rerun after a spec declaration build: 1/1 files, 10/10 tests passed. No failure reaches the diff.", "mcp_calls": "8 — reads only: issue_read x4 (both cards and comments), list_pull_requests x1, pull_request_read get_files x3; 0 writes", "api_writes": "3 — relay pr_create (#33222, assignee os-tesla); post-stamped comment on #33207; post-stamped comment on #33206", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · noted, not filed: AppPlugin's no-metadata basic-insert fallback writes seeds with SEED_WRITE_OPTIONS and no organization; it stamps nothing either, so it was left as is (Acceptance notes).", "carrier: none · noted, not filed: platform-managed tables with no organization column other than sys_user/sys_organization keep a refused probe on an organization's load; now reported once, no scope rule invented." ] }One PR for both cards (#33207 and #33206): #33222, head
3c36f9c6e. It owes a contract review at CONTRACT_REVIEW_TIER (narrowing) from the new review agent before it can land.objectstack-fleet commentedon Nov 14, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Release: session_01L9qibzuZJBQY4wUQFJbkfx · seat domain:server#7 signs off on the maintainer's word 「所有座位当前任务处理完,合并后就下班」 (2026-11-14T04:35Z, project chat) and 「各座位自己的契约复审也暂停,等新的 review agent 上线后复审」 (2026-11-14T04:37Z) · back to pm:queue for the next claimant · 2026-11-14T06:11Z
Resume-from: claude/project-thread-e226dr@3c36f9c6edfb7f70ef9143b0e65bc1fb036d49d8
CONTRACT_REVIEW_TIER(narrowing) from the new review agent, then ACCEPT and land.tenantIdis composed at the call site, the spec constant is untouched, and the organization isconfig.organizationId ?? fallbackOrgId.objectstack-fleet commentedon Nov 14, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Claim: resume after the 04:35Z sign-off, card thread · 2026-11-14T15:22Z · the seat is back on duty for review and landing only (「你现在可以自己契约复审了。」 2026-11-14T14:17Z; 「车道座位应该使用pm-dispatch 技能审核和落地pr」 2026-11-14T14:32Z); one card thread with #33207, PR #33222
Session:
session_01L9qibzSAkofrHDU1U8yFeAAccount:
os-teslaBranch:
claude/project-thread-e226drWorktree:
objectstack-issue-33207Domain:
domain:serverSeat:
domain:server#7File surface: unchanged — claim 6871417580 with amendment 6871533047; PR #33222 touches
seed-loader.ts, its two tests, the seed docs page and one changeset. Stop on breach.Container & model:
L,mode:thread,model: default (dispatch-gates --tier: no path-derived mandate; PM default),effort: highClause-②: yes (narrowing), as declared on PR #33222 and reviewed at
CONTRACT_REVIEW_TIERMetadata-protocol: none
Responsibility: the seed loader's reference probe and seed write context (
metadata-protocol,seed-loader.ts), objectstack | a downstream app shipping sample data as standard seeds | every org-scoped seed loadThread-read: 6879156542
Premise: re-measured by the card thread on 41de6f5 (records 6878779800, 6879099931, 6879156542)
Serial constraints cleared: no other open PR of this seat names
seed-loader.ts