Skip to content

Commit a7b8876

Browse files
feat(spec,metadata-protocol): the publish receipt names the rows a seed created and the rows it only matched (#30427)
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K <!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBY35nWXYXKyBvnYcvZDpPht)_ Fixes #30348 Ruling-ref: objectstack-ai/cloud#3598 (6651768430) Clause-②: yes (widening: an optional, capped per-object `landed` key on both `seedApplied` receipts, and `insertedIds` on the seed loader's per-dataset result) Metadata-protocol: added Skills-docs: none owed — no skill, eval or doc teaches `seedApplied` or `landedIds` (`git grep -n "seedApplied\|landedIds" -- skills content/docs ':!content/docs/references' ':!content/docs/releases'`: 0 hits; control `git grep -c "publish" -- skills/objectstack-ai/SKILL.md`: 5); the generated references (`content/docs/references/api/protocol.mdx`, `data/seed-loader.mdx`) are regenerated here. The skills lane is paused; nothing is owed to it from this PR. Prior rulings read: seedapplied,landedids,publish receipt → 1 hits; ADR-0038 Decision §4; thread: none; repo: objectstack-ai/objectstack ## 维护者速读 - 改了什么:两个发布接口的回执 `seedApplied` 新增可选字段 `landed`,按对象分两张 id 列表:`inserted`(本次发布新建的行)和 `matched`(种子按自然键匹配到的已有行),每张最多 200 个 id,超出时 `truncated: true` 并给出未截断的 `total`;种子加载器结果新增 `insertedIds`。 - 为什么改:执行 cloud#3598 裁决 A。cloud 的「清理示例数据」需要知道发布写了哪些行,回执原来只给条数。拆成两张表是因为默认 `ignore` 模式下,种子会"落"在键相同的客户已有行上而不写它;只给一张表会让 cloud 删掉客户自己的行。 - 风险与回滚:纯增字段、可选,旧回执原样解析;代价是公开发布响应里出现行 id(受 200 上限约束,裁决已接受)。回滚即还原本 PR。cloud 只能清理 `inserted` 里的行,且要跨多次发布累积,因为再次发布时早先插入的行会出现在 `matched`。 - 先看:`packages/metadata-protocol/src/seed-loader.ts` 的 `insertedRecordIndexes`(两个插入点);`packages/metadata-protocol/src/seed-applied-landed.ts`(合并、去重、截断);`packages/spec/src/api/protocol.zod.ts` 的 `SeedAppliedLandedSchema` 与上限常量。 - 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run src/publish-seed-receipt-landed-ids.test.ts`,预期 9 例全绿(含客户已有行只进 `matched`、超限截断、空种子给两张空表、无种子无字段)。 - 需要你做的:无,席位落地。 ## What changes - **`@objectstack/spec` (`packages/spec/src/api/protocol.zod.ts`): `seedApplied.landed`, one shared schema on both publish responses.** - `SeedAppliedLandedSchema`: `{ object, inserted, matched }`, each list a `SeedAppliedIdListSchema` `{ ids, total, truncated }` with all three required. `ids.max(SEED_APPLIED_LANDED_IDS_CAP)`, in the loader's record order (datasets in load order, records as authored), not the order the writes completed (the loader buffers inserts and flushes them after later skips). - `SEED_APPLIED_LANDED_IDS_CAP = 200` (source below). - `PublishMetaItemResponseSchema.seedApplied.landed` and `PublishPackageDraftsResponseSchema.seedApplied.landed` both point at it, with one description text. Optional: absent means "not reported", never "nothing landed". - The `PublishMetaItemResponseSchema` docblock (the three conditional receipts) names the key, the two lists, the cap, the truncation shape and the accepted cost. - **`@objectstack/spec` (`packages/spec/src/data/seed-loader.zod.ts`): `SeedLoadResult.insertedIds`**, the ids among `landedIds` whose row the load created. `landedIds`' TSDoc now states what each mode puts in it (below). - **`@objectstack/metadata-protocol`:** - `seed-loader.ts`: the two insert sites (the batched insert flush and the self-referencing sequential `writeRecord` with `action === 'inserted'`) mark the record index beside `noteLanded`; the result carries `insertedIds` in record order, deduped. - `seed-applied-landed.ts` (new, exported as `seedAppliedLanded`): folds the loader's per-dataset results into one entry per object, in first-loaded order; ids keep the loader's record order (datasets in load order, records as authored, first occurrence kept), not the order the writes completed. Several datasets of one object merge; ids dedupe; an id any dataset of the object inserted is never also `matched`; each list is capped. An object whose datasets landed nothing still gets an entry with two empty lists. - `applySeedBodies` returns `landed: seedAppliedLanded(r.results)` on the path where the loader returned. `publishPackageDrafts` and `publishMetaItem` (through `runPublishSideEffects`) pass it through; their inline return types gain the key. - **`@objectstack/runtime` (`domains/packages.ts`)**: the route-level fallback apply (`applyPublishedSeeds`, for protocols that do not self-apply) returns the same `landed` through the same exported projection. - Generated artifacts regenerated: `authorable-surface/{api,data}.json`, `authorable-defaults/data.json`, `json-schema.manifest/api.json`, `api-surface`, `export-origins`, `declaration-map`, the strictness-ledger counts, and `content/docs/references/{api/protocol,data/seed-loader,index}.mdx`. - `.changeset/30348-seed-receipt-landed-ids.md`: spec minor, metadata-protocol minor, runtime patch. ### What "landed" means, per seed mode (measured at `seed-loader.ts`, every `noteLanded` site) | Mode | `inserted` | `matched` | Not listed | | --- | --- | --- | --- | | `insert` / `replace` | every row an insert created | none | a record whose insert failed | | `ignore` (default since #30081) | rows created for records no existing row matched | existing rows a record's key matched, left as they were | a failed insert | | `upsert` | rows created | rows updated, rows matched and left unchanged (no-op replay), rows matched whose update the engine refused | a failed insert | | `update` | none | rows updated, matched and unchanged, or refused | a record that matched no row | A dry run lists nothing. A matched row whose update was refused is listed because the loader notes it before the write (the row exists and its key resolved). **Consequence for cloud (cloud#3598):** only `inserted` names rows the seed created. A `matched` row may be a customer's own record that shares the seed's natural key; under `ignore` it is matched without being written. ⛔ Never clear a `matched` row on this receipt's word. `inserted` is per publish: a row an earlier publish inserted shows as `matched` on a re-publish, so the ledger must accumulate `inserted` ids across publishes rather than read the latest receipt alone. ### The cap and its source `SEED_APPLIED_LANDED_IDS_CAP = 200` ids per list, per object. The card asked for the cloud ledger's cap on `seeded_record_ids`; objectstack-ai/cloud could not be read from this session (no repository access; an org-wide code search for `seeded_record_ids` returned 0 hits). There is no platform precedent for a bounded id list on a response, so the number follows the platform's existing bounds on a record-id list: `EXPLAIN_BATCH_MAX_RECORD_IDS = 200` (`packages/spec/src/security/explain.zod.ts:257`, the cap on record ids crossing the public API) and the seed loader's 200-row insert batch (`BULK_BATCH_SIZE`). The truncation shape follows `SearchAllResponseSchema.truncated`: declared, never silent. Chosen with spec seat 3 before building. If cloud's ledger cap differs, aligning it is a one-constant change. **Accepted cost (ruling A):** row ids appear on the public `POST /packages/:id/publish-drafts` and `POST /meta/:type/:name/publish` responses, bounded by the cap. ## Why AI builds land their sample rows when the seed draft is published. The loader computes which rows it landed on (`landedIds`), but `applySeedBodies` returned only `success`, `inserted` and `updated`, so cloud's "clear sample data" could not find the rows (measured on a hosted kernel at 1a3ea0c, cloud#3598 report 6650239213). Ruling A puts the ids on the receipt. Splitting them into `inserted` and `matched` is what keeps the clear safe under the create-only default. ## Verification Head `8dea3777` (`c438aed4` plus the review round's test-and-wording commit `8dea3777`; branch merged with `origin/main` `fe835b49`), under `os-verify-lock`, `NODE_OPTIONS=--max-old-space-size=4096`: - `pnpm --filter @objectstack/spec test`: Test Files 813 passed (813), Tests 25580 passed, 1 todo (at `c438aed4`). `typecheck` green for spec, metadata-protocol and runtime (runtime's `check:test-typecheck` included, after a full `turbo run build`, 119/119). - `pnpm --filter @objectstack/metadata-protocol test`: Test Files 313 passed, 4 skipped (317), Tests 22398 passed; `pnpm --filter @objectstack/objectql test`: Test Files 551 passed (551), Tests 10975 passed; `pnpm --filter @objectstack/runtime test`: Test Files 542 passed (542), Tests 8644 passed, 19 skipped (all at `11b7c671`, the implementation commit; the merge brought no change to these files). The new pins re-ran green at `c438aed4`: objectql 3 files 26 passed, `seed-loader-landed-ids.test.ts` 14 passed. At `8dea3777` (review round): `seed-applied-landed.test.ts` + `seed-loader-landed-ids.test.ts` 23 passed, `publish-seed-receipt-landed-ids.test.ts` 9 passed, spec `protocol.test.ts` 242 passed after a full `turbo run build` (119/119); `check:generated` green after `gen:docs` (the reworded `ids` description). - `check-changeset-no-major` with this body as the event: exit 0 on `Metadata-protocol: added`; the control with `none` exits 1 (`authorable-surface`/`authorable-defaults` baselines moved). - New pins: - `packages/objectql/src/publish-seed-receipt-landed-ids.test.ts` (9, real protocol + real ObjectQL engine + real loader over an in-memory driver; every id read back from the engine): a fresh seed lists exactly the engine's rows under `inserted`; under `ignore` a customer's pre-existing row matched by key is `matched` and the fresh record `inserted`; a re-publish lists the first publish's rows as `matched` and nothing `inserted`; 205 records give the first 200 ids in record order, `truncated: true`, `total: 205`, and the schema refuses 205 ids; two seeds of one object fold into one entry; a seed with `records: []` reports one entry with two empty lists (reported-and-empty, not absent); a publish with no seed carries no `seedApplied`; a load that threw reports counts and no `landed`; the single-item `publishMetaItem` door carries the same `landed` and `PublishMetaItemResponseSchema` keeps it. Both response schemas parse the real responses. - `packages/metadata-protocol/src/seed-applied-landed.test.ts` (9, the projection without an engine): junk entries (null, missing or non-string `object`) skipped; no results give `[]`; a dataset that landed nothing (or predates `insertedIds`) gives two empty lists; one dataset's split; exactly 200 ids carried whole with `truncated: false`, 201 cut to 200 with `total: 201`, `truncated: true`, and each list cut on its own; across datasets of one object a row one inserted and another matched stays `inserted` only, whichever order; objects in first-loaded order and ids deduped, first occurrence kept. Its output parses through `SeedAppliedLandedSchema`. - `packages/metadata-protocol/src/seed-loader-landed-ids.test.ts` (+6): `insertedIds` on a fresh load equals `landedIds`; a replay (skip and update) inserts nothing; `ignore` over a pre-existing row; a refused update is landed, not inserted; the self-referencing sequential path; a refused insert and a dry run. - `packages/spec/src/api/protocol.test.ts` (+4): both receipts carry `landed`; existing receipts parse unchanged and gain no key; the cap (200 parses, 201 refused); `total`, `truncated` and both lists are required. - Existing pins updated: `publish-meta-response-conformance.test.ts` (the real seed publish now asserts the `landed` entry) and `publish-package-drafts-seed-receipt-counts.test.ts` (two `toEqual`s on the success receipt admit `landed`). <details><summary>Reverse verification (three mutations, each restored by scripts/ablation-replace.mjs to the HEAD blob)</summary> 1. The batched insert site's mark removed (`if (internalId) insertedRecordIndexes.add(recordIndex);` deleted from `seed-loader.ts`, anchor 1 → 0, blob `4140f681fe20` → `d5fc404362b8`): `seed-loader-landed-ids.test.ts` **3 failed | 11 passed (14)** (fresh load, `ignore` over a pre-existing row, refused insert). The metadata-protocol tests import the loader from `src`. Restored: blob == HEAD `4140f681fe20`, `git diff HEAD` empty. 2. The matched/inserted split removed in the projection (`[...landed].filter((id) => !inserted.has(id))` → `[...landed].filter(() => true)`, blob `76e34e96d4fe` → `e694847b8e36`), `@objectstack/metadata-protocol` rebuilt and `ablation-dist-preflight.mjs` confirming the marker in 2 built files: `publish-seed-receipt-landed-ids.test.ts` **6 failed | 2 passed (8)** (the two green are the no-seed and threw-load pins, which hold either way). Restored, rebuilt, `--absent` preflight green, the file back to 8 passed. A first attempt at this mutation was a no-op (the replacement text already occurred once; the tool refused and restored), so its run read the unmutated dist and is discarded. 3. The cap boundary moved (`truncated: ids.length > SEED_APPLIED_LANDED_IDS_CAP` → `>=` in `seed-applied-landed.ts`, blob `1884875a7f21` → `47cf0511a4af`): `seed-applied-landed.test.ts` **1 failed | 8 passed (9)**, the exactly-200 pin. The test imports the projection from `src`. Restored: blob == HEAD `1884875a7f21`, `git diff HEAD` empty. </details> <details><summary>Gates: dispatch-gates --commands, 107 derived at 8dea377, 107 run, 0 NOT-MEASURED</summary> - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran gates-ran.list` at `8dea3777` (re-run in full after the review round; the same 107 also ran all-green at `c438aed4`): "107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED", exit 0. Every line `exit 0`; `check-changeset-no-major`, `check:generated`, `check:liveness`, `check:spec-parsed-alias` and `check:nul-bytes` included. - The first pass at `11b7c671` had `check:liveness` and `check:generated` red on origin/main's unanchored `query/searchFields` citation (fixed on main by #30368, merged in here), and three exit-3 prerequisites (`check-plugin-teardown-shape --self-test`: pinned fixture fetched; `check:skill-examples`, `check:dual-build-cjs-loads`: no dists). All green on `c438aed4` after the merge and a full `turbo run build` (119/119). - `Prior rulings read` is from `node scripts/pm/check-prior-rulings.mjs --card 30348 --terms seedApplied,landedIds,"publish receipt"`; ADR-0038 Decision §4 (L3 probes attach to the publish response "like `seedApplied`") does not rule on the receipt's ids. - No `!` in the title and no `(narrowing)` arm: no full dogfood run. CI owns the rest of the farm. </details> ## Acceptance notes - A load that THROWS (a seed naming a missing object, a storage outage mid-dataset) reports `inserted`/`updated` counts from `seedLoadPartialWrites` but no ids, so `landed` is absent there. Carrying ids on that path would mean widening `SeedLoadPartialWrites`; not done here, and cloud must read an absent `landed` beside `success: false` as "rows may have landed, ids unknown". - Contract review (spec seat 3, PASS) should-fix 4, the ids of datasets that landed before a mid-batch throw being dropped with the rest, is the case in the note above; the seat files it as a follow-up, so it stays out of this PR. - Two producer tests use a mocked `applySeedBodies` (`protocol-publish-package-drafts.test.ts`), so their `toEqual` receipts are unchanged. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic (one constant and schemas; the projection lives in metadata-protocol). - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes (CI-owned run). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 block excepted, per project convention). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzi6PCniByMTFP6RQ6P --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 989ae42 commit a7b8876

27 files changed

Lines changed: 951 additions & 20 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/metadata-protocol': minor
4+
'@objectstack/runtime': patch
5+
---
6+
7+
feat(spec,metadata-protocol): the publish receipt's `seedApplied.landed` names the rows a seed created and the rows it only matched, per object, capped at 200 ids per list
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
- **`@objectstack/spec`: `seedApplied.landed`** on both publish responses (`PublishMetaItemResponseSchema`, `PublishPackageDraftsResponseSchema`), one shared schema `SeedAppliedLandedSchema`: per object, `inserted` (the rows this publish created) and `matched` (pre-existing rows a seed record's key matched: updated, left as they were, or refused an update), each a `SeedAppliedIdListSchema` `{ ids, total, truncated }`. `ids` carries at most `SEED_APPLIED_LANDED_IDS_CAP` (200) ids, in the loader's record order (datasets in load order, records as authored); past it `truncated` is `true` and `total` is the uncut count. Absent `landed` means "not reported" (a load that threw, or a producer that reports no ids), never "nothing landed". Only `inserted` names rows the seed created: a `matched` row may be a customer's own record that shares the seed's natural key, and under `ignore` (the default) such a row is matched without being written.
13+
- **`@objectstack/spec`: `SeedLoadResult.insertedIds`**, the ids among `landedIds` whose row the load created. Per load: a row an earlier load inserted is matched on a replay, so a reader tracking a seed's rows across publishes accumulates `insertedIds` over every load. `landedIds`' TSDoc now says what each seed mode puts in it.
14+
- **`@objectstack/metadata-protocol`**: `applySeedBodies` returns `landed`, so `publishPackageDrafts` and `publishMetaItem` both carry it; the loader records which landed rows it inserted. `seedAppliedLanded(results)` is exported, the one projection both receipt producers fold the loader's ids through.
15+
- **`@objectstack/runtime`**: the package-publish door's fallback seed apply (for protocols that do not self-apply) reports the same `landed`.
16+
- Accepted cost (objectstack-ai/cloud#3598 ruling A): row ids appear on the public publish responses, bounded by the cap.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 47 additions & 3 deletions
Large diffs are not rendered by default.

‎content/docs/references/data/seed-loader.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ Result of loading a single dataset
164164
| **referencesDropped** | `integer` | optional (default: `0`) | Reference fields dropped from records that were still written |
165165
| **summariesStale** | `integer` | optional (default: `0`) | Roll-up summary values left stale by writes for this dataset |
166166
| **landedIds** | `string[]` | optional (default: `[]`) | Ids of the stored rows this dataset landed on: inserted, updated, or matched on replay |
167+
| **insertedIds** | `string[]` | optional (default: `[]`) | Ids among landedIds whose row this load created (a successful insert); every other landed id was a pre-existing row a record key matched |
167168
| **errors** | `{ sourceObject: string; field: string; targetObject: string; targetField: string; … }[]` | optional (default: `[]`) | Reference resolution errors |
168169

169170
### Nested Shape: `SeedLoadResult.errors[number]`
@@ -302,6 +303,7 @@ Result of loading a single dataset
302303
| **referencesDropped** | `integer` | optional (default: `0`) | Reference fields dropped from records that were still written |
303304
| **summariesStale** | `integer` | optional (default: `0`) | Roll-up summary values left stale by writes for this dataset |
304305
| **landedIds** | `string[]` | optional (default: `[]`) | Ids of the stored rows this dataset landed on: inserted, updated, or matched on replay |
306+
| **insertedIds** | `string[]` | optional (default: `[]`) | Ids among landedIds whose row this load created (a successful insert); every other landed id was a pre-existing row a record key matched |
305307
| **errors** | `{ sourceObject: string; field: string; targetObject: string; targetField: string; … }[]` | optional (default: `[]`) | Reference resolution errors |
306308

307309
### Nested Shape: `SeedLoaderResult.errors[number]`

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Protocol reference — every schema by module
33
navTitle: Protocol Reference
4-
description: Every schema published by @objectstack/spec — 1623 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1625 schemas across 14 protocol modules
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with
2121
| Module | Pages | Schemas | Description |
2222
| :--- | ---: | ---: | :--- |
2323
| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
24-
| [API Protocol](/docs/references/api) | 36 | 486 | REST contracts, endpoints, routing, realtime, batch, discovery. |
24+
| [API Protocol](/docs/references/api) | 36 | 488 | REST contracts, endpoints, routing, realtime, batch, discovery. |
2525
| [Automation Protocol](/docs/references/automation) | 16 | 81 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2626
| [Data Protocol](/docs/references/data) | 31 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
2727
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
@@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
3434
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3535
| [System Protocol](/docs/references/system) | 39 | 295 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3636
| [UI Protocol](/docs/references/ui) | 16 | 175 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **210** | **1623** | 14 protocol modules |
37+
| **Total** | **210** | **1625** | 14 protocol modules |
3838

3939
---
4040

@@ -63,7 +63,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations.
6363

6464
## API Protocol
6565

66-
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **36 pages, 486 schemas**
66+
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **36 pages, 488 schemas**
6767

6868
REST contracts, endpoints, routing, realtime, batch, discovery.
6969

@@ -92,7 +92,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
9292
| [`package-api-assembled.zod.ts`](/docs/references/api/package-api-assembled) | `AssembledInstalledPackage`, `GetInstalledPackageResponse`, `InstalledPackageAtEitherStage`, `ListInstalledPackagesResponse` |
9393
| [`package-lifecycle.zod.ts`](/docs/references/api/package-lifecycle) | `DiscardPackageDraftsResponse`, `DuplicatePackageResponse`, `EnsureCustomizationPackageRequest`, `EnsureCustomizationPackageResponse`, `GetPackageCustomizationsRequest`, `GetPackageCustomizationsResponse`, `ImportUnmanagedPackageRequest`, `ImportUnmanagedPackageResponse`, `ListPackageCommitsResponse`, `PackageExportBundle`, `PackageExportManifest`, `PackagePublishResult`, `ReassignOrphanedMetadataResponse`, `RevertPackageCommitResponse`, `RollbackToPackageCommitResponse` |
9494
| [`plugin-rest-api.zod.ts`](/docs/references/api/plugin-rest-api) | `ErrorHandlingConfig`, `OpenApiGenerationConfig`, `RequestValidationConfig`, `ResponseEnvelopeConfig`, `RestApiEndpoint`, `RestApiPluginConfig`, `RestApiRouteCategory`, `RestApiRouteRegistration`, `ValidationMode` |
95-
| [`protocol.zod.ts`](/docs/references/api/protocol) | `AiAgentCapabilities`, `AiAgentChatRequest`, `AiAgentSummary`, `AiAgentsResponse`, `AiChatRequest`, `AiChatResponse`, `AiCompleteRequest`, `AiConversation`, `AiMessage`, `AiModelsResponse`, `AiPendingAction`, `AiPendingActionStatus`, `AiStreamChunk`, `ApproveAiPendingActionResponse`, `AuditMetaItemRequest`, `AuditMetaItemResponse`, `AutomationActionsResponse`, `AutomationTriggerRequest`, `AutomationTriggerResponse`, `BatchDataRequest`, `BatchDataResponse`, `CheckPermissionRequest`, `CheckPermissionResponse`, `CloneDataResponse`, `CreateAiConversationRequest`, `CreateDataRequest`, `CreateDataResponse`, `CreateManyDataRequest`, `CreateManyDataResponse`, `DeleteDataRequest`, `DeleteDataResponse`, `DeleteManyDataRequest`, `DeleteManyDataResponse`, `DeleteMetaItemRequest`, `DeleteMetaItemResponse`, `DiffMetaItemResponse`, `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `FindDataRequest`, `FindDataResponse`, `FindReferencesToMetaResponse`, `FlowRegistration`, `FlowRegistrationRefusal`, `GetDataRequest`, `GetDataResponse`, `GetDiscoveryRequest`, `GetDiscoveryResponse`, `GetEffectivePermissionsRequest`, `GetEffectivePermissionsResponse`, `GetFieldLabelsRequest`, `GetFieldLabelsResponse`, `GetLocalesRequest`, `GetLocalesResponse`, `GetMetaDiagnosticsResponse`, `GetMetaItemCachedRequest`, `GetMetaItemCachedResponse`, `GetMetaItemLayeredRequest`, `GetMetaItemLayeredResponse`, `GetMetaItemRequest`, `GetMetaItemResponse`, `GetMetaItemsRequest`, `GetMetaItemsResponse`, `GetMetaTypesRequest`, `GetMetaTypesResponse`, `GetNotificationPreferencesRequest`, `GetNotificationPreferencesResponse`, `GetObjectPermissionsRequest`, `GetObjectPermissionsResponse`, `GetPackageRequest`, `GetPackageResponse`, `GetPresenceRequest`, `GetPresenceResponse`, `GetPublishedMetaItemResponse`, `GetRecordFieldAccessResponse`, `GetTranslationsRequest`, `GetTranslationsResponse`, `GetUiViewRequest`, `GetUiViewResponse`, `HistoryMetaItemRequest`, `HistoryMetaItemResponse`, `HttpFindQueryParams`, `InstallPackageRequest`, `InstallPackageResponse`, `ListAiConversationsRequest`, `ListAiConversationsResponse`, `ListAiPendingActionsRequest`, `ListAiPendingActionsResponse`, `ListDraftsResponse`, `ListNotificationsRequest`, `ListNotificationsResponse`, `ListPackagesRequest`, `ListPackagesResponse`, `MarkAllNotificationsReadRequest`, `MarkAllNotificationsReadResponse`, `MarkNotificationsReadRequest`, `MarkNotificationsReadResponse`, `MetadataAuthoringClient`, `MetadataAuthoringSurface`, `MetadataConflictError`, `Notification`, `NotificationPreferences`, `PublicFormSubmitResponse`, `PublishMetaItemRequest`, `PublishMetaItemResponse`, `PublishPackageDraftsRequest`, `PublishPackageDraftsResponse`, `RealtimeConnectRequest`, `RealtimeConnectResponse`, `RealtimeDisconnectRequest`, `RealtimeDisconnectResponse`, `RealtimeSubscribeRequest`, `RealtimeSubscribeResponse`, `RealtimeUnsubscribeRequest`, `RealtimeUnsubscribeResponse`, `RegisterDeviceRequest`, `RegisterDeviceResponse`, `RejectAiPendingActionResponse`, `RollbackMetaItemResponse`, `RuntimeAuthoringIssue`, `SaveMetaItemRequest`, `SaveMetaItemResponse`, `SearchAllHit`, `SearchAllPageHit`, `SearchAllResponse`, `SetPresenceRequest`, `SetPresenceResponse`, `UninstallPackageRequest`, `UninstallPackageResponse`, `UnregisterDeviceRequest`, `UnregisterDeviceResponse`, `UpdateAiConversationRequest`, `UpdateDataRequest`, `UpdateDataResponse`, `UpdateManyDataRequest`, `UpdateManyDataResponse`, `UpdateNotificationPreferencesRequest`, `UpdateNotificationPreferencesResponse`, `ValidateDataIssue`, `ValidateDataRequest`, `ValidateDataResponse` |
95+
| [`protocol.zod.ts`](/docs/references/api/protocol) | `AiAgentCapabilities`, `AiAgentChatRequest`, `AiAgentSummary`, `AiAgentsResponse`, `AiChatRequest`, `AiChatResponse`, `AiCompleteRequest`, `AiConversation`, `AiMessage`, `AiModelsResponse`, `AiPendingAction`, `AiPendingActionStatus`, `AiStreamChunk`, `ApproveAiPendingActionResponse`, `AuditMetaItemRequest`, `AuditMetaItemResponse`, `AutomationActionsResponse`, `AutomationTriggerRequest`, `AutomationTriggerResponse`, `BatchDataRequest`, `BatchDataResponse`, `CheckPermissionRequest`, `CheckPermissionResponse`, `CloneDataResponse`, `CreateAiConversationRequest`, `CreateDataRequest`, `CreateDataResponse`, `CreateManyDataRequest`, `CreateManyDataResponse`, `DeleteDataRequest`, `DeleteDataResponse`, `DeleteManyDataRequest`, `DeleteManyDataResponse`, `DeleteMetaItemRequest`, `DeleteMetaItemResponse`, `DiffMetaItemResponse`, `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `FindDataRequest`, `FindDataResponse`, `FindReferencesToMetaResponse`, `FlowRegistration`, `FlowRegistrationRefusal`, `GetDataRequest`, `GetDataResponse`, `GetDiscoveryRequest`, `GetDiscoveryResponse`, `GetEffectivePermissionsRequest`, `GetEffectivePermissionsResponse`, `GetFieldLabelsRequest`, `GetFieldLabelsResponse`, `GetLocalesRequest`, `GetLocalesResponse`, `GetMetaDiagnosticsResponse`, `GetMetaItemCachedRequest`, `GetMetaItemCachedResponse`, `GetMetaItemLayeredRequest`, `GetMetaItemLayeredResponse`, `GetMetaItemRequest`, `GetMetaItemResponse`, `GetMetaItemsRequest`, `GetMetaItemsResponse`, `GetMetaTypesRequest`, `GetMetaTypesResponse`, `GetNotificationPreferencesRequest`, `GetNotificationPreferencesResponse`, `GetObjectPermissionsRequest`, `GetObjectPermissionsResponse`, `GetPackageRequest`, `GetPackageResponse`, `GetPresenceRequest`, `GetPresenceResponse`, `GetPublishedMetaItemResponse`, `GetRecordFieldAccessResponse`, `GetTranslationsRequest`, `GetTranslationsResponse`, `GetUiViewRequest`, `GetUiViewResponse`, `HistoryMetaItemRequest`, `HistoryMetaItemResponse`, `HttpFindQueryParams`, `InstallPackageRequest`, `InstallPackageResponse`, `ListAiConversationsRequest`, `ListAiConversationsResponse`, `ListAiPendingActionsRequest`, `ListAiPendingActionsResponse`, `ListDraftsResponse`, `ListNotificationsRequest`, `ListNotificationsResponse`, `ListPackagesRequest`, `ListPackagesResponse`, `MarkAllNotificationsReadRequest`, `MarkAllNotificationsReadResponse`, `MarkNotificationsReadRequest`, `MarkNotificationsReadResponse`, `MetadataAuthoringClient`, `MetadataAuthoringSurface`, `MetadataConflictError`, `Notification`, `NotificationPreferences`, `PublicFormSubmitResponse`, `PublishMetaItemRequest`, `PublishMetaItemResponse`, `PublishPackageDraftsRequest`, `PublishPackageDraftsResponse`, `RealtimeConnectRequest`, `RealtimeConnectResponse`, `RealtimeDisconnectRequest`, `RealtimeDisconnectResponse`, `RealtimeSubscribeRequest`, `RealtimeSubscribeResponse`, `RealtimeUnsubscribeRequest`, `RealtimeUnsubscribeResponse`, `RegisterDeviceRequest`, `RegisterDeviceResponse`, `RejectAiPendingActionResponse`, `RollbackMetaItemResponse`, `RuntimeAuthoringIssue`, `SaveMetaItemRequest`, `SaveMetaItemResponse`, `SearchAllHit`, `SearchAllPageHit`, `SearchAllResponse`, `SeedAppliedIdList`, `SeedAppliedLanded`, `SetPresenceRequest`, `SetPresenceResponse`, `UninstallPackageRequest`, `UninstallPackageResponse`, `UnregisterDeviceRequest`, `UnregisterDeviceResponse`, `UpdateAiConversationRequest`, `UpdateDataRequest`, `UpdateDataResponse`, `UpdateManyDataRequest`, `UpdateManyDataResponse`, `UpdateNotificationPreferencesRequest`, `UpdateNotificationPreferencesResponse`, `ValidateDataIssue`, `ValidateDataRequest`, `ValidateDataResponse` |
9696
| [`push-delivery.zod.ts`](/docs/references/api/push-delivery) | `PushDeliveryData`, `PushRoute` |
9797
| [`push-relay.zod.ts`](/docs/references/api/push-relay) | `PushRelayDelivery`, `PushRelayError`, `PushRelayErrorResponse`, `PushRelayPayload`, `PushRelayPriority`, `PushRelayRequest`, `PushRelayResponse`, `PushRelayResult`, `PushRelayResultStatus` |
9898
| [`query-adapter.zod.ts`](/docs/references/api/query-adapter) | `ODataQueryAdapter`, `OperatorMapping`, `QueryAdapterConfig`, `QueryAdapterTarget`, `RestQueryAdapter` |

‎docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.
1919

2020
| Dir | Sites |
2121
|---|---|
22-
| `api/` | 499 |
22+
| `api/` | 501 |

‎packages/metadata-protocol/src/index.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export { recordNotFoundError } from './protocol.js';
1515
// still be quoted (a malformed seed body). Exporting is enabling-only — no
1616
// behaviour in this package changes.
1717
export { clientFacingFailureText, seedRequestValidationError } from './protocol.js';
18+
// [#30348] The `seedApplied.landed` projection (rows created vs matched, per
19+
// object, capped), exported for the same second producer: the runtime's
20+
// fallback seed apply reports on the same receipt, so it folds the loader's
21+
// ids through this one function instead of a copy.
22+
export { seedAppliedLanded, type SeedLandingResult } from './seed-applied-landed.js';
1823
// [#7823] The write-response half of the `internal: true` guarantee — THE
1924
// single helper every generic write ingress routes its response records
2025
// through (A-prime ruling, 2026-08-13). Tripwire-enforced; see the module

‎packages/metadata-protocol/src/protocol-history-and-publish.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import type { MutationProjectionOutcome } from './protocol-mutation-events.js';
4545
import { withPendingAudit } from './protocol-audit-entry.js';
4646
import type { PublishMetaItemRequest } from './protocol-door-requests.js';
4747
import type { ProtocolHost } from './protocol-host.js';
48+
import type { SeedAppliedLandedParsed } from '@objectstack/spec/api';
4849

4950
export class ProtocolHistoryAndPublishFacet {
5051
/** The body of `ObjectStackProtocolImplementation#historyMetaItem`; the method's docblock there is its contract. */
@@ -162,6 +163,8 @@ export class ProtocolHistoryAndPublishFacet {
162163
updated: number;
163164
error?: string;
164165
errors?: unknown[];
166+
/** [#30348] Per object, the rows created vs matched — `SeedAppliedLandedSchema`. */
167+
landed?: SeedAppliedLandedParsed[];
165168
};
166169
/**
167170
* Present when a publish-time materializer is registered for this type
@@ -344,7 +347,7 @@ export class ProtocolHistoryAndPublishFacet {
344347
version: string;
345348
seq: number;
346349
message?: string;
347-
seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[] };
350+
seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[]; landed?: SeedAppliedLandedParsed[] };
348351
materializeApplied?: PublishMaterializeResult;
349352
projectionApplied?: MutationProjectionOutcome;
350353
advisories?: RuntimeAuthoringIssue[];
@@ -1044,12 +1047,12 @@ export class ProtocolHistoryAndPublishFacet {
10441047
actor?: string;
10451048
skipSeedApply?: boolean;
10461049
}): Promise<{
1047-
seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[] };
1050+
seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[]; landed?: SeedAppliedLandedParsed[] };
10481051
materializeApplied?: PublishMaterializeResult;
10491052
projectionApplied?: MutationProjectionOutcome;
10501053
}> {
10511054
const out: {
1052-
seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[] };
1055+
seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[]; landed?: SeedAppliedLandedParsed[] };
10531056
materializeApplied?: PublishMaterializeResult;
10541057
projectionApplied?: MutationProjectionOutcome;
10551058
} = {};

0 commit comments

Comments
 (0)