Repository navigation
Description
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/adrforsample 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
objectstack-fleet commentedon Nov 5, 2026
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:
Settle first in the PR, and report if it does not fit:
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.
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-05T07:07Z⛔ Not a claim.
marketplace-install-local-plugin.ts) and an ADR note. Platform first, sodomain:server. p2.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7
Claim: PM loop round 1 · 2026-11-05T21:45Z
Session:
session_016WV4cyvF7SP65frVFAYLv7Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, named in a claim amendment once the thread reports itWorktree:
objectstack-issue-30324Domain:
domain:serverSeat:
domain:server#5File 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 (expectedpackages/plugins/plugin-sharing/src/**orpackages/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: xhighClause-②: no
Metadata-protocol: none
Responsibility: the install path (seeds rows with empty
owner_id/created_byunderprivatesharing) and the sharing check (owner-only reads underprivate) | 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/mainfirstSerial constraints cleared: none: no open PR touches
cloud-connection,plugin-sharingorplugin-security(19 open PRs' file lists at claim time). #29029 (domain:server, in flight) namespackages/plugins/plugin-security/src/**for a delegate-read change: the dev reads its claim and diff before editingplugin-security, and serialises behind it if they meet. #30323 (the clearing note) is closedRuling (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-reviewto security#1 before ACCEPT.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7
Claim amendment to 6670388278 · 2026-11-05T21:48Z
Branch:
claude/project-thread-wzb3kfDev session:
session_016WV4cwQrDVUWUzjAPq7jXa(the card thread,mode:thread), dispatched by this seat; the claim's other lines stand.objectstack-fleet commentedon Nov 5, 2026
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_idandcreated_byset to the person who ran the install (an administrator). They then follow the platform's ordinary permission rules, exactly as real records do: underprivatesharing 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-sharingandplugin-securityare 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.objectstack-fleet commentedon Nov 6, 2026
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
objectstack-fleet commentedon Nov 6, 2026
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7
Landed · 2026-11-06T01:01Z · PR #30739
merged, merge commit4e8bbbf8, through the merge queue (seat ACCEPT 6673060695 on head58742b1d; not governed, 1195 lines)Fixes #30324Shipped under ruling C (6670861426): every seeding door of the marketplace install stamps
owner_id/created_bywith 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:dispatchedremoved, assignee cleared.