Skip to content

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

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). Present on main at 64551047.

Symptom

On a per-organization replay or a sample-data reseed, a seed record that names a person (owner_id: 'Zhang Wei (Rep)') never resolves, even when that user exists with exactly that name. The load reports Deferred reference unresolved after pass 2 for every such reference, and the rows land owned by the installer / platform owner instead of the named person. On the single-tenant boot seed (no organization on the load) the same reference resolves (control).

Producer

packages/metadata-protocol/src/seed-loader.ts, resolveFromDatabase (the external-key probe loop, ~line 1805 onward):

const where: Record<string, unknown> = { [probeField]: value };
if (organizationId) where.organization_id = organizationId;   // applied to EVERY target object
const records = await this.engine.find(targetObject, { where, … });
…
} catch {
  // Target object (or this probe's column) may not exist — try the next probe
}

sys_user is tenant-less (no organization_id column; identity lives above the organization, membership in sys_member). The SQL driver refuses the unknown column (Find operation failed object sys_user … names a column the database could not resolve), the bare catch swallows it as "try the next probe", every probe fails the same way, and the reference stays unresolved.

Why it matters

Hosted sample data can never be dealt to named people: even if the people are provisioned first (by an admin import, or once #33113 is ruled), every owner reference in an org-scoped load resolves to nobody. It also hides the cause — the warning says "unresolved", not "the probe was refused".

Suggested fix shape (for the lane to judge)

Apply the organization filter only to targets that carry tenancy (the platform's tenancy table already adjudicates each platform object, cf. #14570); for a tenant-less identity target such as sys_user, scope instead through membership in the load's organization (sys_member), so a cross-org person is still never borrowed. And narrow the catch so a refused probe is reported as a refusal, not silently treated as "no match" (same family as #9071, closed, where the probe queried a column the target did not have).

Downstream evidence (steedos-labs/os-project-blackcat-crm PR #59, test/carbon-demo-data.test.ts): after signing up the demo people under the seeded names, seed-replayer(org, { reseed: true }) reports 14 Deferred reference unresolved after pass 2 for the owner references; the boot seed resolves the same names.

Dedupe: is:issue resolveFromDatabase organization (4 hits: #14570, #9071, #12753, #9165 — none on a tenant-less target under an org-scoped load). New. Dedupe words: seed reference sys_user organization_id · resolveFromDatabase owner_id · deferred reference unresolved replay.

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:48Z

    ⛔ 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:03Z · p1 per triage 6870470576 (a downstream app on published 18.4.0 hit it); one card thread with #33206, 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: 6870470576
    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 6871410059 on #33207 · 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": 33207,
      "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 #33206, 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 6871410059 with amendment 6871524022; 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