Skip to content

[Decision] An installed app's sample data has no owner, so a colleague opening the app sees none of it #30324

Closed

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

Filing category ②: only the maintainer can decide this. Analysis-tier: seat tier, by the maintainer's word 「不要用 fable」.

维护者速读

装好 HotCRM 后,管理员能看到示例客户和收入,但同事打开应用,看到的是空的:实测管理员 11 个客户,普通成员 0 个。你说要带初始数据,「这样客户才能看得到效果」。现在只有管理员看得到效果。要你定:示例数据该让谁看到。我建议 A:持有这个应用默认权限集的人,都能只读看到示例数据。

The question

A company installs an app to try it. The person who installed it sees sample data. Their colleagues, given the app's suggested permission, see an empty app. Who should see the samples?

Governing text:

  • The maintainer's word on sample data: 「我觉得安装应用市场应用的时候是需要有初始数据的,这样客户才能看得到效果」 (2026-11-05).
  • packages/cloud-connection/src/marketplace-install-local-plugin.ts, step 5: seed datasets run now "so the installed app has demo data on first paint".
  • Searched docs/adr for sample data: 0038, 0045 and 0067 mention it, and I did not find a rule on who should see it.

Protocol: option A or B may add a declaration. Whether that needs a spec change is for the dev to report first.

Measured

On HotCRM v6.0.0, on a local test deployment, re-check by reading crm_account as each user:

Who Accounts visible
Dev Admin, who installed it 11
A member, before the grant 403
A member, after 授予 of crm_user to Everyone 0, and creating one works (201)

The seeded rows have owner_id and created_by empty, and HotCRM's objects use private sharing. The member sees only what they own, and nobody owns the samples. crm_opportunity reads the same 0. I measured this through the data API for that member; I did not take a screenshot of their dashboard.

Options

Option What it does What the customer sees Work
A. Members with the app's permission can read the samples Install marks the rows as sample data, and the sharing check lets anyone holding the app's permission read them, read-only A team trial shows data to everyone. Clearing removes it for everyone Server and security lanes, one PR or two; no precedent found
B. App authors declare it A package says how its sample data is shared, for example with a sharing rule in its own metadata Depends on each author. HotCRM would need a change Spec, server and app, several PRs; no precedent found
C. Give the rows to the installer Install sets owner_id to the person installing They own the samples. Colleagues still see nothing Small, one PR; no precedent found
D. Leave it and say so The install note says only administrators can see sample data No change in what anyone sees, and no surprise One small PR

In business terms: A is a shared demo store that everyone can walk through. B lets each vendor choose. C hands the demo to one person. D puts up a sign.

Recommendation: A

四轴(中文):

  • 项目长远合理性: 示例数据的目的就是演示,平台统一处理「示例数据谁能看」,每个应用都不用再写一遍。B 要每个作者都想到这件事,大多数人想不到。
  • 实际业务需求: 真实场景是部门试用:管理员装好,叫同事来看。同事看到空白,会以为应用坏了或没装好,试用就断了。C 和 D 解决不了这个场景。无反馈数据(产品尚无多少真实客户),按场景判断。
  • 防 AI 写代码犯错: 出错时谁看到什么很重要。A 必须严格限定在标记为示例的行、并且只读,否则就是静默放宽权限,客户的真实数据可能被看到。这个风险要靠测试钉死:真实行绝不能因此可读。B 把这件事交给 AI 写的元数据,容易漏写,漏了就是静默的空白。
  • 创业阶段不扩散: A 不给作者增加新配置,比 B 少一个契约面,但比 D 多一段权限逻辑。

Blind spot: A touches the record sharing path, so it needs the security lane's review. I have not measured what happens to a sample row a member then edits, or to rows that reference it.

只看①选 A;②③④ 是否翻转:否。

os-decision-facets
① 项目长远合理性: A narrows special cases; one platform rule instead of one per app.
② 实际业务拉动: a department trial where colleagues open the installed app; no field reports yet, argued from the scenario.
③ 防 AI 犯错: A is a platform rule rather than metadata an AI can forget; it must be pinned to sample rows and read-only so a failure cannot silently expose real rows.
④ 创业阶段不扩散: no new author-facing key under A; D is the smallest and leaves the trial broken.

After the ruling

The seat dispatches one card to the server lane with the security lane reviewing, and the install note wording follows the sibling cleanup card.

Blocks nothing.

Activity

  1. added
    enhancementNew feature or request
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

    Ruling, the maintainer verbatim: 「选 A」 (2026-11-05, direct session), answering the options on this card.

    Decided. Members holding an installed app's permission can read that app's sample data, read-only. Clearing removes it for everyone.

    Conditions the fix must hold, from this card's recommendation:

    • Only rows the install marked as sample data are affected. A real record never becomes readable through this, and a test pins that.
    • Read-only: this grants no edit, delete or transfer.
    • The security lane reviews the change, since it touches the record sharing path.

    Settle first in the PR, and report if it does not fit:

    • Whether this needs a spec declaration. If it changes a contract's shape, stop and report.
    • What a member sees for a sample row they cannot edit, and how a sample row behaves when a real record references it. Neither is measured yet.

    Status. Out of the decision box. Ready for triage to route. #30323 (the clearing note) stays a separate card and does not wait for this one.

  3. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 5, 2026
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:queue · 2026-11-05T07:07Z

    ⛔ Not a claim.

    • Read fresh. Ruled A (「选 A」, recorded on this card): members who hold an installed app's permission can read its sample data, which today has no owner, so a colleague sees none of it. The fix lives in the install path (marketplace-install-local-plugin.ts) and an ADR note. Platform first, so domain:server. p2.

    Generated by Claude Code

  5. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Claim: PM loop round 1 · 2026-11-05T21:45Z
    Session: session_016WV4cyvF7SP65frVFAYLv7
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
    Worktree: objectstack-issue-30324
    Domain: domain:server
    Seat: domain:server#5
    File surface: the install path that seeds an app's sample rows (packages/cloud-connection/src/marketplace-install-local-plugin.ts, and the seeding helper it calls, named in the dev's first report) to mark them as sample data; the record read check that lets a holder of the app's permission read marked rows read-only (expected packages/plugins/plugin-sharing/src/** or packages/plugins/plugin-security/src/**, named before editing); their tests; one changeset per PR; an ADR note only if the dev's measurement shows one is owed (then a separate Tier H PR). ⛔ packages/spec/** (a new declaration = stop and report; the ruling asks this to be settled first), ⛔ ui/** (stop on breach; explain in the report)
    Container & model: L, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the install path (seeds rows with empty owner_id/created_by under private sharing) and the sharing check (owner-only reads under private) | none (no platform path makes sample rows readable; C/D were the alternatives the ruling rejected) | every colleague of the installer on a team trial; measured on the card on HotCRM v6.0.0 (admin 11 accounts, member 0)
    Thread-read: 6651996019
    Premise: holds per the card's measurement; the dev re-measures a member's read of seeded rows on origin/main first
    Serial constraints cleared: none: no open PR touches cloud-connection, plugin-sharing or plugin-security (19 open PRs' file lists at claim time). #29029 (domain:server, in flight) names packages/plugins/plugin-security/src/** for a delegate-read change: the dev reads its claim and diff before editing plugin-security, and serialises behind it if they meet. #30323 (the clearing note) is closed

    Ruling (6651650982, the maintainer verbatim 「选 A」): members holding an installed app's permission can read that app's sample data, read-only; clearing removes it for everyone. Conditions: only rows the install marked as sample data are affected, and a test pins that a real record never becomes readable through this; read-only (no edit, delete or transfer); the security lane reviews the change. Settle first and report if it does not fit: whether a spec declaration is needed (a contract shape change = stop and report); what a member sees for a sample row they cannot edit, and how a sample row behaves when a real record references it.

    Security note: this widens record read access under a recorded ruling. Every negative pin is ablated (put the forbidden read back, show red, restore). The seat sends Request: contract-review to security#1 before ACCEPT.

  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Claim amendment to 6670388278 · 2026-11-05T21:48Z
    Branch: claude/project-thread-wzb3kf
    Dev session: session_016WV4cwQrDVUWUzjAPq7jXa (the card thread, mode:thread), dispatched by this seat; the claim's other lines stand.

  8. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Ruling recorded: option C replaces option A. 2026-11-05T22:10Z

    The maintainer, verbatim, in the Claude Code project "Objectstack", thread 「Server 车道派发(席5 #28677)」, 2026-11-05: 「示例数据 是否建议写入的时候带上 负责人和创建人,比如就是管理员」, then, on the seat's answer that HotCRM's private sharing would still hide admin-owned rows from colleagues: 「HotCRM 是私有共享,记录的主人是管理员,同事照样看不到,这个按照平台同意的权限规则就可以。」

    Decided. An installed app's sample rows are written with owner_id and created_by set to the person who ran the install (an administrator). They then follow the platform's ordinary permission rules, exactly as real records do: under private sharing a colleague does not see them, and that is accepted. No sample-data read rule is added. This replaces ruling A (6651650982) and its conditions; plugin-sharing and plugin-security are not touched.

    Left for the dev to measure and report, not to decide:

    The claim (6670388278) stands with its surface narrowed to the seeding path in packages/cloud-connection/src/** and its tests; Clause-②: no, Metadata-protocol: none. The security-lane review the old ruling required no longer applies, since no read access widens.

  9. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cwQrDVUWUzjAPq7jXa
    os-dev-report

    {
      "issue": 30324,
      "status": "done",
      "branch": "claude/project-thread-wzb3kf",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30739",
      "head": "58742b1d994edc24e006b92d8577424a20ed4c6e",
      "session": "session_016WV4cwQrDVUWUzjAPq7jXa",
      "premise_still_valid": true,
      "summary": "Ruling C (6670861426): after each install-local seed load, a new stampSeedOwner (packages/cloud-connection/src/marketplace-install-local-seed-owner.ts) writes owner_id and created_by = the door's person on the rows that load created (SeedLoadResult.insertedIds, never landedIds), only where each field is still NULL, as predicate writes over chunks of 50 ids under {isSystem, skipAutomations} with a row-by-row fallback, skipping sys_*/managed/external objects and scoping to organization_id under a wall. runInlineSeed, the one loader run behind all four seeding doors, takes the person its door names: install and reseed = the caller; boot heal = the ledger's installedBy when a system sys_user read finds them; per-organization replay = options.userId (the founder). A door with no person stamps nothing and logs why. No read rule was added; plugin-sharing, plugin-security, spec and the seed loader are untouched; the earlier sharing-arm edits were discarded uncommitted (never pushed). Not routed through claimSeedOwnership: it runs on app:seeded and reads seed-settlement seededRecordIds (this seed emits neither), writes owner_id only, and targets the platform admin, not the seeder; its selector discipline (only ids the loader reports) is kept, narrowed to insertedIds.",
      "tests": "pnpm --filter @objectstack/cloud-connection test: 71 files, 906 tests passed (new marketplace-install-local-seed-owner.test.ts: 16 tests, the stamp over an in-memory engine plus each door's person). cloud-connection typecheck exit 0. packages/cli/test/package-install-local-sample-owner.integration.test.ts: 6/6 passed in 38.4 s (two real os start boots on one home: install owner = installer; ACCEPTED colleague holding the default set under private sharing reads 0 and by-id 404; installer PATCH 200 / DELETE 200 as owner; purge deleted 3 and kept the colleague's own deal; reseed owner = caller; restart heal owner = recorded installer). Ablations via scripts/ablation-replace.mjs, restore proven against HEAD each time: landedIds-for-insertedIds -> matched-row pin + 4 door pins red; drop still-NULL term -> authored-owner pin red; drop organization term -> walled-install, replay and wall pins red; accept any string as person -> no-person pin red; drop platform/managed skip and (separately) external skip -> skips pin red each; heal without user lookup -> heal no-installer pin red; replay inventing a person -> replay no-founder pin red; e2e no stamp (dist rebuilt, proven by ablation-dist-preflight) -> install/reseed/heal owner pins red; e2e fixture public_read -> colleague-reads-none pin only red; e2e purge key broken (dist proven) -> purge pin only red. Controls after every restore: unit 16/16, e2e 6/6, dist rebuilt and proven free of markers.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 69 families at head 58742b1d; each run with its exit code captured before any pipe: 69/69 exit 0. --ran reconciliation: 69 run, 0 NOT-MEASURED, 0 UNRUN, reconcile exit 0. pnpm lint exit 0. CI check-runs on PR #30739 not yet concluded at report time; the PR is watched and CI failures will be fixed on the branch.",
      "line_budget": {
        "insertions": 1193,
        "deletions": 2,
        "files": 6,
        "by_file": {
          ".changeset/30324-install-sample-rows-owner.md": [
            22,
            0
          ],
          "packages/cli/test/package-install-local-sample-owner.integration.test.ts": [
            383,
            0
          ],
          "packages/cloud-connection/src/marketplace-install-local-plugin.ts": [
            112,
            2
          ],
          "packages/cloud-connection/src/marketplace-install-local-seed-owner.test.ts": [
            431,
            0
          ],
          "packages/cloud-connection/src/marketplace-install-local-seed-owner.ts": [
            240,
            0
          ],
          "scripts/engine-double-contract.pinned.json": [
            5,
            0
          ]
        },
        "production_code": [
          352,
          2
        ],
        "tests": [
          814,
          0
        ],
        "other": [
          27,
          0
        ]
      },
      "files_changed": [
        ".changeset/30324-install-sample-rows-owner.md",
        "packages/cli/test/package-install-local-sample-owner.integration.test.ts",
        "packages/cloud-connection/src/marketplace-install-local-plugin.ts",
        "packages/cloud-connection/src/marketplace-install-local-seed-owner.test.ts",
        "packages/cloud-connection/src/marketplace-install-local-seed-owner.ts",
        "scripts/engine-double-contract.pinned.json"
      ],
      "deviations": [
        "scripts/engine-double-contract.pinned.json (+1 row, outside the ruled surface): written by `check:engine-double-contract --write` because the new test's fake engine has an update verb; gate-owned ledger, no hand edit.",
        "The end-to-end pins live in packages/cli/test (the real os start producer lives there), not in packages/cloud-connection; the ruling's surface said 'and tests'.",
        "The integration test's test-budget header (60 s) is from a local reading (44.6 s x 1.25, rounded up); there is no CI reading yet.",
        "Doors with no person, reported not invented: the boot heal when the ledger records no installedBy or that user no longer exists, and the per-organization replay when no founder is handed in. Both leave rows unowned and log an info line naming why.",
        "AppPlugin's replayer, when it is the registered seed-replayer instead of this plugin's, does not go through this code (outside the ruled surface, not changed); whether its rows get an owner was not measured.",
        "No backfill: sample rows seeded before this change keep NULL owner (reseed does not touch rows that already exist); purge then reseed is the path, stated in the changeset.",
        "Worktree /home/user/objectstack-issue-30324 is kept, not removed, while the PR is watched for CI and review fixes."
      ],
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "4 — mcp__github__issue_read x2, mcp__github__list_pull_requests x2; reads only, no write tool",
      "api_writes": "2 — relay fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, PR #30739), and this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/30324/comments); plus git push of the branch (not a REST write)",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  10. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Landed · 2026-11-06T01:01Z · PR #30739

    Probe Reading
    PR #30739 merged, merge commit 4e8bbbf8, through the merge queue (seat ACCEPT 6673060695 on head 58742b1d; not governed, 1195 lines)
    #30324 closed by Fixes #30324

    Shipped under ruling C (6670861426): every seeding door of the marketplace install stamps owner_id / created_by with the person it names, on the rows its load created; ordinary permission rules apply. Not covered, named for the maintainer: AppPlugin's own per-organization replayer and the other ownerless boot-time seeds (package install, publish seed apply, dev hot-reload), with the seat's earlier audit finding awaiting the maintainer's word to file.

    State change: pm:dispatched removed, assignee cleared.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions