Skip to content

Commit 5483b24

Browse files
committed
feat(spec,metadata-protocol): put the runtime authoring gate's advisory findings on the save response (#4717)
#4463 put the shared author-time rule registry on the runtime write path — the fourth door, and for a Studio tenant or an MCP/AI author the ONLY one. It gated on `error` findings only; the advisory half was produced, walked into a `console.warn` deduped once per process, and dropped. PR #4715 named that when it shipped: running a rule and discarding its conclusion is a smaller version of the hole the gate exists to close. The case is reachable, not theoretical. A flow whose only defect is a `delete_record` node declaring `multi: true` with no `filter` yields `errors = 0 / advisories = 1`: the write succeeds, the row persists, and the author never learns their nightly sweep deletes every row on every run. - `RuntimeAuthoringIssueSchema` declares the finding shape ONCE, in spec — the same `rule`/`path`/`where`/`message`/`hint`/`severity` the 422 envelope carries (#4463 D3). `metadata-protocol` re-exports it as its `RuntimeAuthoringIssue` instead of keeping a parallel hand-written interface. - `SaveMetaItemResponseSchema.advisories` — optional, array of that element. - `evaluateRuntimeAuthoringGate` returns a `RuntimeAuthoringVerdict` (`{ error, advisories }`) instead of `Error | null`. An ADDED return channel: the success path previously returned `null` and had nowhere to put a verdict. - `saveMetaItem` attaches the advisories, emitting the key ONLY when non-empty — never `[]` — so a clean save's response bytes are unchanged. `rulesRun` stays off: the gate appends a gate-local rule id for `flow`, so not every id it would list resolves in the lint registry. Save door only; the publish door's asymmetry is stated in the changeset. Fixes #4717 Claude-Session: https://claude.ai/code/session_01W6bLax4KMrSfnE1ydFU8Dw Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f16e54e commit 5483b24

14 files changed

Lines changed: 603 additions & 44 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/metadata-protocol": minor
4+
---
5+
6+
feat(spec,metadata-protocol): the runtime authoring gate's advisory findings reach the save response (#4717)
7+
8+
#4463 put the shared author-time rule registry on the runtime write path — the
9+
fourth door, and for a Studio tenant or an MCP/AI author the ONLY one, because a
10+
`sys_metadata` overlay row is not in the CLI's config file and there is no
11+
`os lint` to run against it. It gated on `error` findings only. The rest — the
12+
advisory half — were produced, walked into a `console.warn` deduped once per
13+
process per `type|name|rule|path`, and then went out of scope. #4715 named that
14+
honestly when it shipped: running a rule and discarding its conclusion is a
15+
smaller version of the hole the gate was built to close.
16+
17+
That case is reachable today, not theoretical. A flow whose only defect is a
18+
`delete_record` node declaring `multi: true` with no `filter` yields
19+
`errors = 0 / advisories = 1`: the write **succeeds**, the row persists, the
20+
flow registers, and the author never learns that their nightly sweep deletes
21+
every row of the object on every run.
22+
23+
**What changed**
24+
25+
- `SaveMetaItemResponseSchema` declares an OPTIONAL `advisories` array, whose
26+
element is the newly-declared `RuntimeAuthoringIssueSchema` — the SAME
27+
`rule` / `path` / `where` / `message` / `hint` / `severity` shape the 422
28+
`invalid_metadata` envelope already carries (#4463 D3, "reuse the Zod
29+
envelope"). It is declared once: `@objectstack/metadata-protocol` re-exports
30+
it as its `RuntimeAuthoringIssue` instead of keeping a second hand-written
31+
interface for the same six keys, so the refusal and the success channel
32+
cannot drift into two dialects.
33+
- `evaluateRuntimeAuthoringGate` returns a `RuntimeAuthoringVerdict`
34+
(`{ error, advisories }`) instead of `Error | null`. This is an ADDED return
35+
channel, not a threaded value: the success path previously returned `null` and
36+
had nowhere to put a verdict at all.
37+
- `saveMetaItem` attaches the advisories to its success response.
38+
39+
**Additive and conditional.** The key is emitted ONLY when at least one advisory
40+
was raised — never as `[]` — so a clean save's response bytes are byte-for-byte
41+
what they were before, and a caller that ignores the field behaves exactly as
42+
today. Absence means "nothing to report", never "the gate did not run".
43+
44+
**`rulesRun` is deliberately NOT on the response.** The gate appends its own
45+
`PLATFORM_SCHEDULE_CREATE_RECORD_ORG_MISSING` when the type is `flow`, so not
46+
every id it would list resolves in the lint registry; exposing the array would
47+
need the declaration to say the ids are *gate* ids. A field can be added later,
48+
not removed.
49+
50+
**⚠️ Save door only — the asymmetry is deliberate, not an oversight.** The gate
51+
runs on BOTH write doors: `saveMetaItem` and the draft→active promotion, on
52+
purpose, so `?mode=draft` followed by publish is not a bypass (#4463 D1).
53+
Studio's designer uses draft-then-publish on every edit, so the publish door is
54+
the dominant Studio flow and it does **not** carry this field yet. That door's
55+
own response contract only just landed (#7294); carrying the advisories over is
56+
tracked separately rather than bundled here, so this change stays one optional
57+
field on one already-declared envelope.
58+
59+
Rendering the findings in Studio is the objectui half of #4717 and is queued in
60+
that repo behind this change.

content/docs/references/api/protocol.mdx

Lines changed: 19 additions & 2 deletions
Large diffs are not rendered by default.

content/docs/references/index.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Protocol Reference
3-
description: Every schema published by @objectstack/spec — 1576 schemas across 14 protocol modules
3+
description: Every schema published by @objectstack/spec — 1577 schemas across 14 protocol modules
44
---
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -20,7 +20,7 @@ counts are sums of the rows they head. Regenerate with
2020
| Module | Pages | Schemas | Description |
2121
| :--- | ---: | ---: | :--- |
2222
| [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
23-
| [API Protocol](/docs/references/api) | 28 | 411 | REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery. |
23+
| [API Protocol](/docs/references/api) | 28 | 412 | REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery. |
2424
| [Automation Protocol](/docs/references/automation) | 13 | 68 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2525
| [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. |
2626
| [Data Protocol](/docs/references/data) | 30 | 166 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
@@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with
3333
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3434
| [System Protocol](/docs/references/system) | 37 | 292 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3535
| [UI Protocol](/docs/references/ui) | 16 | 147 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36-
| **Total** | **200** | **1576** | 14 protocol modules |
36+
| **Total** | **200** | **1577** | 14 protocol modules |
3737

3838
---
3939

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

6262
## API Protocol
6363

64-
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **28 pages, 411 schemas**
64+
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **28 pages, 412 schemas**
6565

6666
REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery.
6767

@@ -86,7 +86,7 @@ REST/GraphQL contracts, endpoints, routing, realtime, batch, discovery.
8686
| [`odata.zod.ts`](/docs/references/api/odata) | `ODataConfig`, `ODataError`, `ODataFilterFunction`, `ODataMetadata`, `ODataQuery`, `ODataResponse` |
8787
| [`package-api.zod.ts`](/docs/references/api/package-api) | `GetInstalledPackageRequest`, `GetInstalledPackageResponse`, `ListInstalledPackagesRequest`, `ListInstalledPackagesResponse`, `PackageApiErrorCode`, `PackageInstallRequest`, `PackageInstallResponse`, `PackagePathParams`, `PackageRollbackRequest`, `PackageRollbackResponse`, `PackageUpgradeRequest`, `PackageUpgradeResponse`, `ResolveDependenciesRequest`, `ResolveDependenciesResponse`, `UninstallPackageApiRequest`, `UninstallPackageApiResponse`, `UploadArtifactRequest`, `UploadArtifactResponse` |
8888
| [`plugin-rest-api.zod.ts`](/docs/references/api/plugin-rest-api) | `ErrorHandlingConfig`, `HandlerStatus`, `OpenApiGenerationConfig`, `RequestValidationConfig`, `ResponseEnvelopeConfig`, `RestApiEndpoint`, `RestApiPluginConfig`, `RestApiRouteCategory`, `RestApiRouteRegistration`, `RouteCoverageEntry`, `RouteCoverageReport`, `ValidationMode` |
89-
| [`protocol.zod.ts`](/docs/references/api/protocol) | `AiAgentCapabilities`, `AiAgentChatRequest`, `AiAgentSummary`, `AiAgentsResponse`, `AiChatRequest`, `AiChatResponse`, `AiCompleteRequest`, `AiConversation`, `AiMessage`, `AiModelsResponse`, `AiPendingAction`, `AiPendingActionStatus`, `AiStreamChunk`, `ApproveAiPendingActionResponse`, `AutomationActionsResponse`, `AutomationTriggerRequest`, `AutomationTriggerResponse`, `BatchDataRequest`, `BatchDataResponse`, `CheckPermissionRequest`, `CheckPermissionResponse`, `CreateAiConversationRequest`, `CreateDataRequest`, `CreateDataResponse`, `CreateManyDataRequest`, `CreateManyDataResponse`, `DeleteDataRequest`, `DeleteDataResponse`, `DeleteManyDataRequest`, `DeleteManyDataResponse`, `DeleteMetaItemRequest`, `DeleteMetaItemResponse`, `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `FindDataRequest`, `FindDataResponse`, `GetDataRequest`, `GetDataResponse`, `GetDiscoveryRequest`, `GetDiscoveryResponse`, `GetEffectivePermissionsRequest`, `GetEffectivePermissionsResponse`, `GetFieldLabelsRequest`, `GetFieldLabelsResponse`, `GetLocalesRequest`, `GetLocalesResponse`, `GetMetaItemCachedRequest`, `GetMetaItemCachedResponse`, `GetMetaItemLayeredResponse`, `GetMetaItemRequest`, `GetMetaItemResponse`, `GetMetaItemsRequest`, `GetMetaItemsResponse`, `GetMetaTypesRequest`, `GetMetaTypesResponse`, `GetNotificationPreferencesRequest`, `GetNotificationPreferencesResponse`, `GetObjectPermissionsRequest`, `GetObjectPermissionsResponse`, `GetPackageRequest`, `GetPackageResponse`, `GetPresenceRequest`, `GetPresenceResponse`, `GetTranslationsRequest`, `GetTranslationsResponse`, `GetUiViewRequest`, `GetUiViewResponse`, `HttpFindQueryParams`, `InstallPackageRequest`, `InstallPackageResponse`, `ListAiConversationsRequest`, `ListAiConversationsResponse`, `ListAiPendingActionsRequest`, `ListAiPendingActionsResponse`, `ListNotificationsRequest`, `ListNotificationsResponse`, `ListPackagesRequest`, `ListPackagesResponse`, `MarkAllNotificationsReadRequest`, `MarkAllNotificationsReadResponse`, `MarkNotificationsReadRequest`, `MarkNotificationsReadResponse`, `Notification`, `NotificationPreferences`, `PublishMetaItemResponse`, `RealtimeConnectRequest`, `RealtimeConnectResponse`, `RealtimeDisconnectRequest`, `RealtimeDisconnectResponse`, `RealtimeSubscribeRequest`, `RealtimeSubscribeResponse`, `RealtimeUnsubscribeRequest`, `RealtimeUnsubscribeResponse`, `RegisterDeviceRequest`, `RegisterDeviceResponse`, `RejectAiPendingActionResponse`, `SaveMetaItemRequest`, `SaveMetaItemResponse`, `SetPresenceRequest`, `SetPresenceResponse`, `UninstallPackageRequest`, `UninstallPackageResponse`, `UnregisterDeviceRequest`, `UnregisterDeviceResponse`, `UpdateAiConversationRequest`, `UpdateDataRequest`, `UpdateDataResponse`, `UpdateManyDataRequest`, `UpdateManyDataResponse`, `UpdateNotificationPreferencesRequest`, `UpdateNotificationPreferencesResponse`, `ValidateDataIssue`, `ValidateDataRequest`, `ValidateDataResponse` |
89+
| [`protocol.zod.ts`](/docs/references/api/protocol) | `AiAgentCapabilities`, `AiAgentChatRequest`, `AiAgentSummary`, `AiAgentsResponse`, `AiChatRequest`, `AiChatResponse`, `AiCompleteRequest`, `AiConversation`, `AiMessage`, `AiModelsResponse`, `AiPendingAction`, `AiPendingActionStatus`, `AiStreamChunk`, `ApproveAiPendingActionResponse`, `AutomationActionsResponse`, `AutomationTriggerRequest`, `AutomationTriggerResponse`, `BatchDataRequest`, `BatchDataResponse`, `CheckPermissionRequest`, `CheckPermissionResponse`, `CreateAiConversationRequest`, `CreateDataRequest`, `CreateDataResponse`, `CreateManyDataRequest`, `CreateManyDataResponse`, `DeleteDataRequest`, `DeleteDataResponse`, `DeleteManyDataRequest`, `DeleteManyDataResponse`, `DeleteMetaItemRequest`, `DeleteMetaItemResponse`, `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `FindDataRequest`, `FindDataResponse`, `GetDataRequest`, `GetDataResponse`, `GetDiscoveryRequest`, `GetDiscoveryResponse`, `GetEffectivePermissionsRequest`, `GetEffectivePermissionsResponse`, `GetFieldLabelsRequest`, `GetFieldLabelsResponse`, `GetLocalesRequest`, `GetLocalesResponse`, `GetMetaItemCachedRequest`, `GetMetaItemCachedResponse`, `GetMetaItemLayeredResponse`, `GetMetaItemRequest`, `GetMetaItemResponse`, `GetMetaItemsRequest`, `GetMetaItemsResponse`, `GetMetaTypesRequest`, `GetMetaTypesResponse`, `GetNotificationPreferencesRequest`, `GetNotificationPreferencesResponse`, `GetObjectPermissionsRequest`, `GetObjectPermissionsResponse`, `GetPackageRequest`, `GetPackageResponse`, `GetPresenceRequest`, `GetPresenceResponse`, `GetTranslationsRequest`, `GetTranslationsResponse`, `GetUiViewRequest`, `GetUiViewResponse`, `HttpFindQueryParams`, `InstallPackageRequest`, `InstallPackageResponse`, `ListAiConversationsRequest`, `ListAiConversationsResponse`, `ListAiPendingActionsRequest`, `ListAiPendingActionsResponse`, `ListNotificationsRequest`, `ListNotificationsResponse`, `ListPackagesRequest`, `ListPackagesResponse`, `MarkAllNotificationsReadRequest`, `MarkAllNotificationsReadResponse`, `MarkNotificationsReadRequest`, `MarkNotificationsReadResponse`, `Notification`, `NotificationPreferences`, `PublishMetaItemResponse`, `RealtimeConnectRequest`, `RealtimeConnectResponse`, `RealtimeDisconnectRequest`, `RealtimeDisconnectResponse`, `RealtimeSubscribeRequest`, `RealtimeSubscribeResponse`, `RealtimeUnsubscribeRequest`, `RealtimeUnsubscribeResponse`, `RegisterDeviceRequest`, `RegisterDeviceResponse`, `RejectAiPendingActionResponse`, `RuntimeAuthoringIssue`, `SaveMetaItemRequest`, `SaveMetaItemResponse`, `SetPresenceRequest`, `SetPresenceResponse`, `UninstallPackageRequest`, `UninstallPackageResponse`, `UnregisterDeviceRequest`, `UnregisterDeviceResponse`, `UpdateAiConversationRequest`, `UpdateDataRequest`, `UpdateDataResponse`, `UpdateManyDataRequest`, `UpdateManyDataResponse`, `UpdateNotificationPreferencesRequest`, `UpdateNotificationPreferencesResponse`, `ValidateDataIssue`, `ValidateDataRequest`, `ValidateDataResponse` |
9090
| [`query-adapter.zod.ts`](/docs/references/api/query-adapter) | `ODataQueryAdapter`, `OperatorMapping`, `QueryAdapterConfig`, `QueryAdapterTarget`, `RestQueryAdapter` |
9191
| [`realtime.zod.ts`](/docs/references/api/realtime) | `RealtimeConfig`, `RealtimeEvent`, `RealtimeEventType`, `RealtimePresence`, `Subscription`, `SubscriptionEvent`, `TransportProtocol` |
9292
| [`realtime-shared.zod.ts`](/docs/references/api/realtime-shared) | `BasePresence`, `PresenceStatus`, `RealtimeRecordAction` |

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ directory rather than per file.
264264
| Dir | Sites |
265265
|---|---|
266266
| `ai/` | 77 |
267-
| `api/` | 397 |
267+
| `api/` | 398 |
268268
| `cloud/` | 83 |
269269
| `identity/` | 33 |
270270
| `integration/` | 10 |

0 commit comments

Comments
 (0)