Skip to content

feat(cloud-connection): count an installed app's remaining sample rows the purge's way - #30613

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-2emmzj
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-2emmzj

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread
domain:server#2 · os-bill · session_01Utp6CYE3Av1KFxjNwT6qzN

维护者速读

  • 改了什么:新增只读接口 GET /api/v1/marketplace/install-local/:manifestId/sample-data,回答「这个已装应用的示例数据现在还剩多少条」,即此刻点「清除示例数据」会删掉的条数。清除本身一行没动。
  • 为什么改:After installing an app, say how much sample data was loaded and offer clearing it right there #30323 的 ui 半边已落地,但只在本次浏览器会话里知道装了多少条;刷新后没有任何接口能给出数量,清除确认框也就说不出会删几条。
  • 风险与回滚:只读,不删数据;和清除共用同一个匹配器、同一道 manage_metadata 权限,所以数量和清除结果一定一致。任何查询参数都会被 400 拒绝,GET 请求无论怎么写都删不了数据。回滚即还原本 PR。
  • 先看:marketplace-install-local-plugin.ts 的 handleSampleDataCount;marketplace-install-local-purge.ts 的 countSeedRows;测试文件末尾新增的 GET …/sample-data describe。
  • 两分钟验证:pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 src/marketplace-install-local-purge.test.ts,预期 33 个全绿,其中 GET …/sample-data 下 7 个。
  • 需要你做的:无,席位落地。

Fixes #30323

Clause-②: yes (widening)
Metadata-protocol: none

What changes

After a reload, no API said how much sample data an installed app still had. The install answer's data.seeded is gone, the purge has no dry run, and the listing's withSampleData stops at the first seed row it finds and answers yes or no. So the console's purge confirm could only give a count for an install made in the same browser session (PR #30537, the ui half of this card).

  • GET /api/v1/marketplace/install-local/:manifestId/sample-data (new, read-only) answers { success: true, data: { manifestId, count, unidentified } }.
    • count is the number of rows POST …/purge-sample-data would delete now: countSeedRows, which runs the purge's own matchSeedRows pass in full (not firstOnly) and deletes nothing.
    • unidentified is the seed records the purge would report in errors and leave in place (no usable key, a key two rows carry, an object whose rows could not be read). Each is logged at warn with its reason.
  • Same rules as the purge, in the same order: manage_metadata through the shared requireInstallCapability (401 / held-session 403 / 403 FORBIDDEN), then 404 for no install, the unreadable-ledger answer, the protocol handshake's 422 OS_PROTOCOL_INCOMPATIBLE, then the purge's scope (organizationWallActive + resolveActiveOrgId).
  • Where it differs from the purge, because it is a read: a walled session with no active organization gets 200, count: 0 and no row is read (ADR-0123 D2 for a tenant-scoped read; the listing answers withSampleData: false to the same session), where the purge refuses 403. A package with no seed datasets gets count: 0, where the purge answers 400 NOTHING_TO_PURGE. No engine or metadata service gets the purge's 500 DRIVER_UNAVAILABLE, never a 0.
  • It cannot delete. It is a GET with an empty closed query set (SAMPLE_DATA_COUNT_QUERY_PARAMS, AGENTS.md route rule 5): ?dryRun=0, ?confirm=true or any other name is refused 400 VALIDATION_ERROR with the shared located sentence (unknownQueryParamRefusalMessage from @objectstack/rest), before any row is read.
  • Route ledger: one server-only row beside the purge in cloud-connection-route-ledger.ts.
  • Dependency: @objectstack/cloud-connection now depends on @objectstack/rest (workspace) for that refusal sentence; pnpm-lock.yaml gains the three importer lines. No cycle: rest does not depend on cloud-connection, and runtime, which cloud-connection already depends on, already depends on rest.
  • Unchanged: what the purge deletes, its gate and its answer; the listing and its withSampleData; the install door. No packages/spec change.

Why a separate read, not a count on the listing

The seat ruling asked to measure both and take the smaller. The listing answers every installed package on every visit to Installed Apps, for every signed-in caller, and today reads only until the first seed row (firstOnly). A count has to read every seeded object's rows for every package on each listing call. The new door does that for one package, when the confirm needs it, behind the purge's gate. A ?dryRun=1 switch on the purge was rejected because a misspelled switch on a POST that deletes would delete.

What "an edited sample record is kept" means

The purge matches a row by its dataset's externalId key. A sample record whose key field was edited is no longer a seed row: it is neither deleted nor counted. A sample record edited only in other fields still carries its seed key: the purge deletes it, and the count includes it. The card's observation ("kept a sample record I had edited") holds for a key edit. The count follows the purge either way, because it is the same matcher; the pins below cover all three cases.

Tests

Pins go into the existing harness files as new top-level describes.

marketplace-install-local-purge.test.ts:

  • countSeedRows: counts exactly the rows purgeSeedRows then deletes, deleting nothing itself; a key two rows carry is unidentified, equal to the purge's errors, and is not counted.
  • GET …/sample-data, through the booted plugin with the real SeedLoaderService:
    • install with samples gives count 2; add a user record, edit the sample contact's key and the sample account's non-key field, and the count is 1; the purge then deletes exactly that one row; afterwards the count is 0 and the listing says withSampleData: false;
    • wall with an active organization: counts only that organization's rows, every read pinned to it;
    • wall with no active organization: 200, count 0, no seed row read;
    • a package without seed datasets: count 0, no row read;
    • ?dryRun=0 and ?confirm=true: 400 VALIDATION_ERROR naming the parameter, nothing read, nothing deleted;
    • no install: 404; no metadata service: 500 DRIVER_UNAVAILABLE;
    • signed in without manage_metadata: 403 FORBIDDEN naming the capability, no seed row read.

marketplace-install-local-sample-data-not-loaded.test.ts: on an entry the rehydrate refused, the count answers the purge's 422 byte for byte and changes nothing; the loadable entry beside it counts 1.

cloud-connection-auth-gate.pin.test.ts and marketplace-install-local-api-key-account-posture.pin.test.ts: the door joins the census (now six), so a held session and a held owner's API key are refused 403 with the gate's code, an anonymous caller 401, and the un-held operator is served.

Acceptance notes

  • The console does not call the new door yet. That is the ui follow-up the seat files: the purge confirm and the banner can read count on any visit.
  • No hand-written doc becomes false. content/docs/deployment/marketplace-handbook.mdx step 6 names the reseed and purge routes and says both need manage_metadata, which still holds. Adding the new route there is left to the ui follow-up, when the console shows the count.
  • The file surface grew by marketplace-install-local-plugin.ts (told to the seat first; handler and one mount line only, the install door and its heldBy read untouched), the two auth-pin test files whose route census must list the new door, packages/cloud-connection/package.json and pnpm-lock.yaml (the @objectstack/rest dependency), and packages/cloud-connection/vitest.config.ts (its source alias).
  • Changeset: patch, because Metadata-protocol: none (AGENTS.md Post-Task Checklist step 3: the level follows the kind; a non-protocol widening ships patch).

Verification

All on head 00d0c9d5 (origin/main ad2393f7 merged at 05371373; the merge touched no packages/cloud-connection file; the dependency closure was rebuilt after it).

Tests and typecheck
  • pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 under the verify lock: Test Files 70 passed (70), Tests 888 passed (888).
  • pnpm --filter @objectstack/cloud-connection typecheck (both tsc programs, tests included): exit 0.
  • eslint --no-inline-config over the seven changed .ts files: exit 0, no finding. The repo-wide pnpm lint is left to CI.
Ablations (scripts/ablation-replace.mjs, each restored, tree clean after each)

The suites import the plugin and the matcher from src/, so no rebuild sits between the mutation and the run.

  1. countSeedRows passes firstOnly: true (the listing's early stop): 2 red, the unit "counts exactly the rows the purge then deletes" and the route "counts N … purge then deletes exactly the count".
  2. The query-parameter refusal deleted: 1 red, "a query parameter is refused with a located 400".
  3. The no-active-organization count: 0 answer deleted: 1 red, "wall, no active organization: 200 with count 0".
  4. The requireInstallCapability admission deleted: 4 red, the manage_metadata 403 pin, both held-session census cases and the anonymous 401 case in cloud-connection-auth-gate.pin.test.ts.
  5. The DRIVER_UNAVAILABLE branch disabled: 1 red, "no metadata service: the purge's 500 DRIVER_UNAVAILABLE, never a 0".
Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 00d0c9d5: 75 families, each run with its exit code captured before any pipe, all exit 0. --ran: 75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN.

On the first run check:test-source-alias was red: the new @objectstack/rest import resolved to rest/dist in the tests. Fixed by an anchored alias in packages/cloud-connection/vitest.config.ts, as the gate prescribes.

Full dogfood battery: n/a. The title has no ! and Clause-② has no (narrowing) arm. CI's Dogfood Regression Gate runs on the PR.


Generated by Claude Code

…s the purge's way

GET /api/v1/marketplace/install-local/:manifestId/sample-data answers how
many rows a purge would delete now (countSeedRows, the purge's matcher with
nothing deleted), behind the purge's gate, with a closed empty query set.

Claude-Session: https://claude.ai/code/session_01Utp6CYE3Av1KFxjNwT6qzN
Co-authored-by: Claude <[email protected]>
@github-actions github-actions Bot added the size/m label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cloud-connection, touching 16 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/cloud-connection/package.json, packages/cloud-connection/vitest.config.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

28 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 55d9dd448f00d1498ba3ae830368306aa0dd8d8a.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/cloud-connection/package.json, packages/cloud-connection/vitest.config.ts) — pages documenting those are invisible to this run
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 55d9dd448f00d1498ba3ae830368306aa0dd8d8a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f45a2499b2d7f4057068c0f595b1223a65d2476f — the merge of head 00d0c9d55607411d933cfc506d6f7e86c101839d into base 55d9dd448f00d1498ba3ae830368306aa0dd8d8a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f45a2499b2d7f4057068c0f595b1223a65d2476f && git checkout f45a2499b2d7f4057068c0f595b1223a65d2476f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 55d9dd448f00d1498ba3ae830368306aa0dd8d8a 00d0c9d55607411d933cfc506d6f7e86c101839d && git checkout -B drift-repro 55d9dd448f00d1498ba3ae830368306aa0dd8d8a && git merge --no-ff 00d0c9d55607411d933cfc506d6f7e86c101839d

node scripts/docs-audit/affected-docs.mjs --json 55d9dd448f00d1498ba3ae830368306aa0dd8d8a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 55d9dd448f00d1498ba3ae830368306aa0dd8d8a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

维护者速读

  • 改了什么: 新增一个只读查询 GET /api/v1/marketplace/install-local/:manifestId/sample-data,回答某个已装应用当前还剩多少示例记录,数字和“清除示例数据”此刻会删的条数完全一致。
  • 为什么改: 控制台只有在本次浏览器会话里装的应用才知道示例数据条数,刷新后就只剩“有/没有”,没法告诉管理员清除会删多少(After installing an app, say how much sample data was loaded and offer clearing it right there #30323)。
  • 风险与回滚: 只读,不删任何东西;清除、列表、安装三处逻辑零改动;权限和清除同一道门。回滚即撤本 PR。
  • 先看: marketplace-install-local-purge.ts 的 countSeedRows(复用清除的同一个匹配器),再看 marketplace-install-local-plugin.ts 的新处理器。
  • 两分钟验证: pnpm --filter @objectstack/cloud-connection test。
  • 需要您做的: 无,席位经合并队列落地(非受管面,395 行,非 BREAKING)。控制台读这个数是 ui 卡 Installed app page: after a reload, show how many sample records are left (read the new server count) instead of only yes/no #30616。
  • 席位意见: ACCEPT。计数和清除用同一个匹配器,不会对不上;新入口只读、无查询参数可传错,删不了数据。

席位核对 — ACCEPT · domain:server#2 · head 00d0c9d55607411d933cfc506d6f7e86c101839d

  • 形态: draft, author objectstack-fleet[bot] (relay pr_create), no assignee, no review request; 11 files, +376 −19; check-governed-merges --pr 30613: NOT governed, 395 lines ≤ 3000. Title feat(cloud-connection), no !. Fixes #30323 (ui half landed in PR feat(console): the install note says how many sample records an install loaded, and clears them in place #30537; the console follow-up is Installed app page: after a reload, show how many sample records are left (read the new server count) instead of only yes/no #30616).
  • Review face: TIER_DEFAULT (Clause-②: yes (widening): one read-only route added; nothing a client reads today moves), written here by the seat.
  • Seat rulings held: shape (b) — a read-only GET, not a dry-run flag on the delete; the purge's own matchSeedRows (full pass, no firstOnly) via countSeedRows; purge, listing and install door unchanged; same gate as the purge (manage_metadata), same 404/422 order; walled-off with no active organization and no-seed packages answer 0; closed empty query set (any parameter 400). marketplace-install-local-plugin.ts gains the handler and one mount only (seat 3's objectql: a declared .custom action override has four door and boot-order loose ends left by PR #30497 #30542 touches the install door there; whichever lands second merges main).
  • Seat read: countSeedRows is a three-line wrapper over matchSeedRows; the docblock records that a key-field edit makes a row not a seed row (counted and purged alike), matching what the purge does. Route ledger row added beside its neighbours. Dependency @objectstack/rest (for the shared unknown-query-parameter refusal sentence): rest does not depend on cloud-connection, no cycle; plugin-auth and organizations already depend on it. vitest.config.ts source alias as check:test-source-alias requires; two auth-census pins count the sixth door.
  • 偏差 (accepted): the rest dependency + lockfile lines, the vitest alias, the census edits — all forced by the route rules, recorded in the report.
  • Dev readings (00d0c9d5): cloud-connection 70 files / 888 tests, typecheck green; derived gates 75, all exit 0, --ran 75/75; 5 ablations red then restored. Full dogfood under the verify lock: VERDICT command-exit 0, 420 files passed / 3 skipped, 3496 tests passed / 36 skipped, 24m41s (report 6665411086 + 6666087144).
  • 门禁 check-runs: read at ACCEPT; the seat queues only when every check is green or an expected skip.
  • 落地条款: Fixes After installing an app, say how much sample data was loaded and offer clearing it right there #30323. Ordinary queue landing by this seat: pr_ready + automerge_enable on this head.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 18:38
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 6e8ec73 Nov 5, 2026
65 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-2emmzj branch November 5, 2026 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

After installing an app, say how much sample data was loaded and offer clearing it right there

1 participant