Skip to content

Commit 8c711fb

Browse files
authored
feat(spec)!: tombstone the seven dead AppSchema authoring keys (#4001 app step, PR A) (#4142)
The 2026-06 AppSchema liveness audit verdicted seven authorable keys DEAD — never read by any consumer in framework or objectui. Authoring them shipped config the author believed was in effect; sharing/embed were the dangerous case: a declared public-access surface no route enforced (ADR-0049 class, the live path is FormView.sharing). mobileNavigation was a mode picker that changed nothing (even packages/mobile ignored it). All seven are retiredKey() tombstones — tsc-level `never` for typed authors, a parse-time prescription for everyone else. NOT a bare deletion: AppSchema is not yet .strict(), so deleting the keys would have reintroduced the exact silent-strip failure mode this campaign eliminates. Wired through the full ADR-0087 removal machinery (the gen:schema tombstone gate enforces this): the app-dead-authoring-keys-removed D2 conversion (a pure lossless delete) joins the protocol-17 chain step, so `os migrate meta` rewrites authored sources and spec-changes.json / the upgrade guide document the removal. Also rewrites the app.test.ts acceptance tests that codified the strip-era fiction, and swaps two stack.test.ts fixtures that used App.objects as a map-format filler key. This clears the enforce-or-remove precondition for PR B (AppSchema + navigation-union .strict() via z.discriminatedUnion). Verified: spec 7034 tests + tsc clean; all 12 check gates (including the migrations/conversions fixture replay); dogfood 73 files / 425 tests; showcase / crm / todo validate clean.
1 parent 7a55913 commit 8c711fb

14 files changed

Lines changed: 264 additions & 119 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
feat(spec)!: tombstone the seven dead AppSchema authoring keys (#4001 app step, PR A)
6+
7+
The 2026-06 AppSchema liveness audit verdicted seven authorable keys DEAD —
8+
never read by any consumer in framework or objectui. Authoring them shipped
9+
config the author believed was in effect; `sharing`/`embed` were the
10+
dangerous case (a declared public-access surface no route enforced,
11+
ADR-0049 class). All seven are now `retiredKey()` tombstones: `tsc`-level
12+
`never` for typed authors, a parse-time prescription for everyone else —
13+
NOT a silent strip, because `AppSchema` is not yet `.strict()`.
14+
15+
**Removed keys and their prescriptions (FROM → TO):**
16+
17+
- `App.version` → an app is versioned by its owning package: `manifest.version`.
18+
- `App.aria` → declare `aria` on the component/widget that renders the DOM node.
19+
- `App.objects` / `App.apis` → the self-described "config file convenience";
20+
objects/apis belong to the stack (`defineStack({ objects, apis })`) — the
21+
chatbot derives an app's object list from its NAV ITEMS, never from these.
22+
- `App.sharing` / `App.embed` → public access is granted per FORM VIEW
23+
(`FormView.sharing`, the public-data-collection surface); no public-app or
24+
iframe route ever read the app-level blocks.
25+
- `App.mobileNavigation` → fully unimplemented (even `packages/mobile`
26+
ignored it); returns if/when a real mobile navigation ships.
27+
28+
Deleting a key is behavior-preserving by construction — none ever had a
29+
runtime effect. `os migrate meta --from <16 or lower>` rewrites your source
30+
(the `app-dead-authoring-keys-removed` conversion, ADR-0087), or delete the
31+
keys by hand.
32+
33+
This clears the ADR-0049 precondition for PR B (AppSchema + navigation-union
34+
`.strict()`): strictness should guard the real contract, not dead keys.

content/docs/references/api/metadata.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const result = AppDefinitionResponse.parse(data);
6868
| **success** | `boolean` || Operation success status |
6969
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| 'INVALID_REFERENCE' \| 'DUPLICATE_VALUE' \| 'INVALID_QUERY' \| 'INVALID_FILTER' \| 'INVALID_SORT' \| 'MAX_RECORDS_EXCEEDED' \| 'UNAUTHENTICATED' \| 'INVALID_CREDENTIALS' \| 'EXPIRED_TOKEN' \| 'INVALID_TOKEN' \| 'SESSION_EXPIRED' \| 'MFA_REQUIRED' \| 'EMAIL_NOT_VERIFIED' \| 'PERMISSION_DENIED' \| 'INSUFFICIENT_PRIVILEGES' \| 'FIELD_NOT_ACCESSIBLE' \| 'RECORD_NOT_ACCESSIBLE' \| 'LICENSE_REQUIRED' \| 'IP_RESTRICTED' \| 'TIME_RESTRICTED' \| 'RESOURCE_NOT_FOUND' \| 'OBJECT_NOT_FOUND' \| 'RECORD_NOT_FOUND' \| 'FIELD_NOT_FOUND' \| 'ENDPOINT_NOT_FOUND' \| 'RESOURCE_CONFLICT' \| 'CONCURRENT_MODIFICATION' \| 'DELETE_RESTRICTED' \| 'DUPLICATE_RECORD' \| 'LOCK_CONFLICT' \| 'METHOD_NOT_ALLOWED' \| 'PRECONDITION_REQUIRED' \| 'RATE_LIMIT_EXCEEDED' \| 'QUOTA_EXCEEDED' \| 'CONCURRENT_LIMIT_EXCEEDED' \| 'INTERNAL_ERROR' \| 'DATABASE_ERROR' \| 'TIMEOUT' \| 'SERVICE_UNAVAILABLE' \| 'NOT_IMPLEMENTED' \| 'EXTERNAL_SERVICE_ERROR' \| 'INTEGRATION_ERROR' \| 'WEBHOOK_DELIVERY_FAILED' \| 'BATCH_PARTIAL_FAILURE' \| 'BATCH_COMPLETE_FAILURE' \| 'TRANSACTION_FAILED' \| 'ACCOUNT_LOCKED' \| 'ALREADY_REVERTED' \| 'AMBIGUOUS_MATCH' \| 'ANALYTICS_QUERY_FAILED' \| 'APPROVAL_ACTIONS_FAILED' \| 'APPROVAL_RECALL_FAILED' \| 'APPROVAL_REQUEST_GET_FAILED' \| 'APPROVAL_REQUEST_LIST_FAILED' \| 'ASYNC_NOT_SUPPORTED' \| 'ATTACHMENT_DELETE_DENIED' \| 'ATTACHMENT_DOWNLOAD_DENIED' \| 'ATTACHMENT_PARENT_ACCESS' \| 'AUDIENCE_NOT_ALLOWED' \| 'AUTH_CONFIG_ERROR' \| 'AUTH_REQUIRED' \| 'AUTOMATION_UNSCOPED_RUN_DATA_ACCESS' \| 'BATCH_ABORTED' \| 'BATCH_NOT_ATOMIC' \| 'BATCH_TOO_LARGE' \| 'BATCH_UNRESOLVED_REF' \| 'BLANK_MATCH_KEY' \| 'CLONE_DISABLED' \| 'CLOUD_FETCH_FAILED' \| 'CLOUD_UNCONFIGURED' \| 'COMMIT_NOT_FOUND' \| 'CONCURRENT_UPDATE' \| 'CONFLICTING_MAPPING' \| 'CONNECTOR_UPSTREAM_UNAVAILABLE' \| 'CREATE_FAILED' \| 'CUBE_NOT_FOUND' \| 'DATASET_INVALID' \| 'DATASOURCE_ADMIN_ERROR' \| 'DELEGABLE_SCOPE_FAILED' \| 'DELIVERY_NOT_ELIGIBLE' \| 'DESTRUCTIVE_CHANGE' \| 'DEVICE_CODE_FAILED' \| 'DOMAIN_VERIFICATION_DISABLED' \| 'DOMAIN_VERIFICATION_FAILED' \| 'DRIVER_UNAVAILABLE' \| 'DUPLICATE_REQUEST' \| 'EMAIL_SEND_FAILED' \| 'EMAIL_SERVICE_REQUIRED' \| 'ENQUEUE_FAILED' \| 'ENVIRONMENT_BIND_FAILED' \| 'ENVIRONMENT_NOT_FOUND' \| 'ENV_ACCESS_DENIED' \| 'ERR_BULK_RESULT_MISMATCH' \| 'ERR_DATASOURCE_UNAVAILABLE' \| 'ERR_DRIVER_CONNECT' \| 'ERR_FILE_CONSTRAINT' \| 'ERR_FILE_REFERENCE_COPY' \| 'ERR_SUMMARY_RECOMPUTE' \| 'EXECUTION_ERROR' \| 'EXPIRED_OR_REVOKED' \| 'EXPIRY_IN_PAST' \| 'EXPIRY_TOO_LONG' \| 'EXPLAIN_FAILED' \| 'EXPORT_NOT_PERMITTED' \| 'EXTERNAL_IMPORT_ERROR' \| 'EXTERNAL_SCHEMA_MISMATCH' \| 'EXTERNAL_SCHEMA_MODE_VIOLATION' \| 'EXTERNAL_WRITE_FORBIDDEN' \| 'FEEDS_DISABLED' \| 'FILES_DISABLED' \| 'FILE_DOWNLOAD_DENIED' \| 'FILE_NOT_FOUND' \| 'FILTER_TOKEN_UNKNOWN' \| 'FILTER_TOKEN_UNRESOLVED' \| 'FORBIDDEN' \| 'FORM_NOT_FOUND' \| 'FORM_RESOLVE_FAILED' \| 'IMPORT_JOB_CREATE_FAILED' \| 'IMPORT_ROW_FAILED' \| 'INTERNAL' \| 'INVALID_EMAIL' \| 'INVALID_EXPIRY' \| 'INVALID_METADATA' \| 'INVALID_OR_EXPIRED' \| 'INVALID_PHONE' \| 'INVALID_REQUEST' \| 'INVALID_RESUME_TOKEN' \| 'INVALID_SIGNAL' \| 'INVALID_SIGNATURE' \| 'INVALID_STATE' \| 'INVITE_EMAIL_FAILED' \| 'INVITE_REQUIRES_EMAIL' \| 'INVITE_SMS_FAILED' \| 'IP_NOT_ALLOWED' \| 'ITEM_LOCKED' \| 'LAST_LOCAL_CREDENTIAL' \| 'LOOKUP_NOT_PUBLIC' \| 'LOOKUP_TARGET_MISSING' \| 'MANIFEST_CONFLICT' \| 'MAPPING_FORMAT_MISMATCH' \| 'MAPPING_FORMAT_UNSUPPORTED' \| 'MAPPING_NOT_FOUND' \| 'MAPPING_TARGET_MISMATCH' \| 'MARKETPLACE_PROXY_FAILED' \| 'MARKETPLACE_STORAGE_FAILED' \| 'MARKETPLACE_UNAVAILABLE' \| 'METADATA_BRANCH' \| 'METADATA_CONFLICT' \| 'METADATA_NOT_FOUND' \| 'METADATA_SCHEMA_INVALID' \| 'MONGODB_MULTI_TENANT_UNSUPPORTED' \| 'NAMESPACE_PREFIX' \| 'NEEDS_PASSWORD' \| 'NODE_FAILURE' \| 'NOTHING_TO_PURGE' \| 'NOT_CREATABLE' \| 'NOT_FOUND' \| 'NOT_OVERRIDABLE' \| 'NOT_UNDOABLE' \| 'NO_DRAFT' \| 'NO_EXECUTOR' \| 'NO_IDENTITY' \| 'NO_MATCH' \| 'NO_PENDING_VERIFICATION' \| 'OAUTH_REGISTER_FAILED' \| 'OBJECT_API_DISABLED' \| 'OBJECT_API_METHOD_NOT_ALLOWED' \| 'OPENAPI_UNAVAILABLE' \| 'OS_PROTOCOL_INCOMPATIBLE' \| 'OVERLAY_PERSISTENCE_FAILED' \| 'PACKAGE_DELETE_FAILED' \| 'PACKAGE_DELETE_PARTIAL' \| 'PACKAGE_MANIFEST_INVALID' \| 'PACKAGE_PUBLISH_FAILED' \| 'PASSWORD_ALREADY_SET' \| 'PASSWORD_EXPIRED' \| 'PASSWORD_POLICY_VIOLATION' \| 'PASSWORD_REUSE' \| 'PAYLOAD_TOO_LARGE' \| 'PERMISSION_NOT_ALLOWED' \| 'PHONE_NOT_ENABLED' \| 'PLUGIN_INSTALL_FAILED' \| 'PLUGIN_MANIFEST_INVALID' \| 'PLUGIN_REGISTER_FAILED' \| 'PROJECT_MEMBERSHIP_REQUIRED' \| 'PROJECT_NOT_FOUND' \| 'PROJECT_PROVISIONING' \| 'PROJECT_PROVISIONING_FAILED' \| 'RAW_SQL_UNSUPPORTED' \| 'RECORD_GONE' \| 'RECORD_LOCKED' \| 'REPORTS_LIST_FAILED' \| 'REPORT_DELETE_FAILED' \| 'REPORT_GET_FAILED' \| 'REPORT_NOT_FOUND' \| 'REPORT_RUN_FAILED' \| 'REPORT_SAVE_FAILED' \| 'REPORT_SCHEDULE_FAILED' \| 'REQUEST_NOT_FOUND' \| 'RESEED_NO_ROWS' \| 'RESEED_SKIPPED' \| 'ROUTE_NOT_FOUND' \| 'RULE_DEFINE_FAILED' \| 'RULE_DELETE_FAILED' \| 'RULE_EVALUATE_FAILED' \| 'RULE_GET_FAILED' \| 'RULE_LIST_FAILED' \| 'RULE_NOT_FOUND' \| 'SAML_REGISTER_FAILED' \| 'SCHEDULES_LIST_FAILED' \| 'SCHEDULE_DELETE_FAILED' \| 'SETTINGS_ACTION_FAILED' \| 'SETTINGS_FORBIDDEN' \| 'SETTINGS_LOCKED' \| 'SETTINGS_UNKNOWN_KEY' \| 'SETTINGS_UNKNOWN_NAMESPACE' \| 'SETTINGS_VALIDATION' \| 'SHARES_LIST_FAILED' \| 'SHARE_GRANT_FAILED' \| 'SHARE_REVOKE_FAILED' \| 'SHARING_NOT_ENABLED' \| 'SIGN_IN_REQUIRED' \| 'SSO_REGISTER_FAILED' \| 'SSO_REGISTER_FORBIDDEN' \| 'SUGGESTION_CONFIRM_FAILED' \| 'SUGGESTION_DISMISS_FAILED' \| 'SUGGESTION_LIST_FAILED' \| 'SUGGESTION_NOT_FOUND' \| 'SUGGESTION_STATE' \| 'SUMMARY_RECOMPUTE_FAILED' \| 'UNAUTHORIZED' \| 'UNIQUE_VIOLATION' \| 'UNKNOWN_KEY' \| 'UNKNOWN_NAMESPACE' \| 'UNSUPPORTED' \| 'UNSUPPORTED_QUERY_PARAM' \| 'UNSUPPORTED_TRANSFORM' \| 'UPLOAD_SESSION_NOT_FOUND' \| 'USER_ALREADY_EXISTS' \| 'VALIDATION_FAILED' \| 'VERSION_NOT_FOUND' \| 'VERSION_NOT_RESTORABLE' \| 'WRITABLE_PACKAGE_REQUIRED' \| 'WRONG_PASSWORD'>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
7070
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
71-
| **data** | `{ name: string; label: string; version?: string; description?: string; … }` || Full App Configuration |
71+
| **data** | `{ name: string; label: string; version?: any; description?: string; … }` || Full App Configuration |
7272

7373

7474
---

content/docs/references/ui/app.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const result = ActionNavItem.parse(data);
7373
| :--- | :--- | :--- | :--- |
7474
| **name** | `string` || App unique machine name (lowercase snake_case) |
7575
| **label** | `string` || App display label |
76-
| **version** | `string` | optional | App version |
76+
| **version** | `any` | optional | [REMOVED] `App.version` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — no consumer in framework or objectui). An app is versioned by its owning package: use `manifest.version`. Delete the key. |
7777
| **description** | `string` | optional | App description |
7878
| **icon** | `string` | optional | App icon used in the App Launcher |
7979
| **branding** | `{ primaryColor?: string; accentColor?: string; logo?: string; favicon?: string }` | optional | App-specific branding |
@@ -85,13 +85,13 @@ const result = ActionNavItem.parse(data);
8585
| **contextSelectors** | `{ id: string; label: string; icon?: string; optionsSource: object; … }[]` | optional | App-level scope dropdowns whose value is injected into nav items as `{<id>}` template vars |
8686
| **homePageId** | `string` | optional | ID of the navigation item to serve as landing page |
8787
| **requiredPermissions** | `string[]` | optional | Permissions required to access this app |
88-
| **objects** | `any[]` | optional | Objects belonging to this app |
89-
| **apis** | `any[]` | optional | Custom APIs belonging to this app |
90-
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration |
91-
| **embed** | `{ enabled?: boolean; allowedOrigins?: string[]; width?: string; height?: string; … }` | optional | Iframe embedding configuration |
92-
| **mobileNavigation** | `{ mode?: Enum<'drawer' \| 'bottom_nav' \| 'hamburger'>; bottomNavItems?: string[] }` | optional | Mobile-specific navigation configuration |
88+
| **objects** | `any` | optional | [REMOVED] `App.objects` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — never read; the spec itself labelled it "config file convenience"). Objects belong to the stack (`defineStack({ objects })`); an app reaches them through its navigation items. Delete the key. |
89+
| **apis** | `any` | optional | [REMOVED] `App.apis` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — never read). Declarative endpoints belong to the stack (`defineStack({ apis })`), not the app shell. Delete the key. |
90+
| **sharing** | `any` | optional | [REMOVED] `App.sharing` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit / ADR-0049 enforce-or-remove) — no public-app route ever read it, so it declared sharing that did not exist. Public access is granted per FORM VIEW (`FormView.sharing`, the public-data-collection surface). Delete the key. |
91+
| **embed** | `any` | optional | [REMOVED] `App.embed` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit / ADR-0049) — no iframe route ever read it. Embedding is a per-form-view surface (`FormView.sharing`), not an app-level switch. Delete the key. |
92+
| **mobileNavigation** | `any` | optional | [REMOVED] `App.mobileNavigation` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — fully unimplemented; no renderer, including packages/mobile, ever read it). Delete the key; the block returns if/when a real mobile navigation ships. |
9393
| **defaultAgent** | `string` | optional | Platform agent bound to this app's ambient chat ('ask' is the implicit default; 'build' for authoring surfaces) — ADR-0063 §1 |
94-
| **aria** | `{ ariaLabel?: string; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes for the application |
94+
| **aria** | `any` | optional | [REMOVED] `App.aria` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — no renderer read app-level ARIA attributes). Declare `aria` on the component/widget that renders the DOM node instead. Delete the key. |
9595
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this app. |
9696
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
9797
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |

content/docs/releases/v17.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,14 @@ somewhere else and did, in three different somewhere-elses: `error.details.code`
789789

790790
### Dead spec clusters removed
791791

792+
**App shell (2026-06 liveness audit, #4001 app step).** `App.version`,
793+
`App.aria`, `App.objects`, `App.apis`, `App.sharing`, `App.embed` and
794+
`App.mobileNavigation` are tombstoned — none was ever read by framework or
795+
objectui. `sharing`/`embed` were the dangerous pair: a declared public-access
796+
surface no route enforced (the live path is `FormView.sharing`).
797+
`mobileNavigation` was a mode picker that changed nothing. Each key rejects
798+
with its prescription; `os migrate meta` deletes them from your source.
799+
792800
Each of these parsed and did nothing. None has a runtime consumer; delete the
793801
import or the authored key.
794802

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ tightening (the #4001 "sharing-rule lesson": candidates, not verdicts).
127127
| `view.zod.ts` | 51 | authorable | partially strict (ADR-0089); long tail of sub-blocks |
128128
| `component.zod.ts` | 29 | authorable | **next candidate** — SDUI component defs; check React-prop open slots first (p) |
129129
| `theme.zod.ts` | 14 | authorable (p) | authored themes |
130-
| `app.zod.ts` | 11 | authorable | **next verified step**`AppSchema` + nav-item union; recursive `NavigationItemSchema` needs union-error care |
130+
| `app.zod.ts` | 11 | authorable | **PR A done (#4001 app step): the seven audit-dead keys (`version`/`aria`/`objects`/`apis`/`sharing`/`embed`/`mobileNavigation`) are `retiredKey()` tombstones + an ADR-0087 conversion**the ADR-0049 precondition for strict. **PR B next**: `AppSchema` + nav union `.strict()`; the union-error question is settled — convert `NavigationItemSchema` to `z.discriminatedUnion('type', …)` (verified: matched-branch-only unknown-key errors, precise recursive paths, `toJSONSchema` clean) |
131131
| `dashboard.zod.ts` | 11 | authorable | partially strict |
132132
| `widget.zod.ts` | 9 | authorable (p) | |
133133
| `page.zod.ts` | 7 | authorable | partially strict (ADR-0089) |
@@ -219,5 +219,10 @@ schemas, with the ADR-0019 re-home map as wrong-layer guidance
219219
(`steps` / `entryCriteria` / `onApprove` / `onReject` / `rejectionBehavior`
220220
each point at where the concept lives on the flow graph now).
221221

222+
Done in the app step, PR A: the seven audit-dead AppSchema keys tombstoned
223+
(`retiredKey` + `app-dead-authoring-keys-removed` conversion + step-17
224+
migration entry), clearing the enforce-or-remove precondition for the app
225+
strict step (PR B).
226+
222227
Long tail stays gated on a verification pass per shape — never a one-shot
223228
"make all ~453 sites strict" (ADR-0054 ratchet; #4001's own recommendation).

docs/protocol-upgrade-guide.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ And it removes the RLS-policy key `priority` (#3896 security audit): promised "c
128128

129129
The same close-out retires the four inert tool authoring keys (`category`, `permissions`, `active`, `builtIn`): none is part of AIToolDefinition and no execution path read them. Two were misleading in the dangerous direction — `permissions` promised an invocation gate nothing enforced, and `active: false` read as "withdrawn" while the tool kept reaching the LLM tool set. Lossless deletes; the strict ToolSchema rejects each with its prescription.
130130

131+
The AppSchema sheds its seven dead authoring keys (2026-06 liveness audit, #4001 app step): `version` (apps are versioned by manifest.version), `aria`, `objects`/`apis` (the self-described "config file convenience" — nothing read them; the chatbot derives an app's objects from its nav items), `sharing`/`embed` (a declared-but-unenforced public surface — the only live path is FormView.sharing; ADR-0049), and `mobileNavigation` (fully unimplemented). Pure lossless deletes — none ever had a runtime effect; each key is tombstoned with its prescription.
132+
131133
ADR-0113 splits the `required` tri-binding: post-17, `required` is ONLY the write-time contract (insert must provide; update may not null out; legacy null rows rest), and the physical NOT NULL is the explicit `storage.notNull`. The `field-required-notnull-explicit` conversion preserves every pre-17 source verbatim-in-meaning by stamping `storage.notNull: true` onto each required field — under the old semantics that column WAS created NOT NULL, so the rewrite writes down what the text already meant. Migration-chain-only (retired from the load path): this is a default flip, not a rename, and a loader that auto-applied it would stamp the constraint onto 17-authored sources that deliberately omit it.
132134

133135
On the wire contract it also retires the `/analytics/query` request ENVELOPE (#3878): `AnalyticsQueryRequestSchema` used to describe `{ cube, query: {...}, format }` — the dialect of the retired degraded analytics shim (#3891) that the real engine never understood (an envelope body inferred a column-less cube and died as an SQL syntax error). The canonical request body is now the BARE AnalyticsQuery — `cube` + `measures` at the top level — which is what every real caller already sends; the schema tombstones `query`/`format`, and the dispatcher entry validates bodies and answers 400 with the prescription. No stored metadata carries this shape (it was HTTP-only), so the change is two semantic TODOs for API callers rather than a stack conversion.
@@ -147,6 +149,7 @@ The close-out sweep finishes the enforce-or-remove worklist across the remaining
147149
| `flow-node-script-config-aliases` | `flow.node.script.config` | script flow-node config keys 'functionName' → 'function', 'input' → 'inputs' (#3796) | live — protocol 17 loader accepts the old shape |
148150
| `permission-rls-priority-removed` | `permission.rowLevelSecurity.priority` | RLS-policy key 'priority' removed (#3896 audit — policies OR-combine, so the promised conflict-resolution semantics cannot exist; dropping it changes no outcome) | retired — `migrate meta` only |
149151
| `tool-inert-authoring-keys-removed` | `tool.category / tool.permissions / tool.active / tool.builtIn` | tool keys 'category'/'permissions'/'active'/'builtIn' removed (#3896 close-out — authorable and inert; permissions gated nothing, active:false withdrew nothing) | retired — `migrate meta` only |
152+
| `app-dead-authoring-keys-removed` | `app.version / app.aria / app.objects / app.apis / app.sharing / app.embed / app.mobileNavigation` | app keys 'version'/'aria'/'objects'/'apis'/'sharing'/'embed'/'mobileNavigation' removed (2026-06 liveness audit — never read; sharing/embed declared a public surface no route enforced, mobileNavigation was fully unimplemented) | retired — `migrate meta` only |
150153
| `field-required-notnull-explicit` | `object.fields.*.required / object.fields.*.storage.notNull` | required fields gain explicit 'storage.notNull: true' (ADR-0113 — pre-17 'required' implied the column constraint; post-17 it is only the write contract) | retired — `migrate meta` only |
151154
| `action-inert-keys-removed` | `action.shortcut / action.bulkEnabled` | action keys 'shortcut'/'bulkEnabled' removed (#3896 close-out — no keydown path dispatches shortcuts; the multi-select toolbar reads the view's bulkActions) | retired — `migrate meta` only |
152155
| `flow-inert-keys-removed` | `flow.active / flow.template / flow.nodes[].outputSchema / flow.errorHandling.fallbackNodeId` | flow keys 'active'/'template', node 'outputSchema' and errorHandling 'fallbackNodeId' removed (#3896 close-out — active:false never stopped a flow; status is the enforced lifecycle) | retired — `migrate meta` only |

0 commit comments

Comments
 (0)