Skip to content

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

Closed

Description

@os-bob

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.

Activity

  1. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    Contributor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p1 · pm:queue · 2026-11-14T02:47Z

    ⛔ Not a claim.

  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Nov 14, 2026
  3. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    Contributor

    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_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

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
  5. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    Contributor

    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 at origin/main 6e1ab9b), 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.

  6. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    Contributor

    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.

  7. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    Contributor

    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

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 14, 2026
  9. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    Contributor

    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_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

  10. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions