-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathapp.json
More file actions
281 lines (281 loc) · 21.4 KB
/
Copy pathapp.json
File metadata and controls
281 lines (281 loc) · 21.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
{
"type": "app",
"_note": "AppSchema — the navigation shell, the densest hand-authored surface on the platform. Consumers: the REST read layer's filterAppForUser (packages/rest/src/rest-server.ts:1808-1888 — the SERVER-side authority for app/nav permission + capability gating and ADR-0045 hidden-app visibility), the spec i18n translateApp (i18n-resolver.ts:472), and objectui's shell (@940ba24: app-shell AppSidebar/ConsoleLayout/ContextSelectors, layout NavigationRenderer, console RootLandingRedirect). The #4001/#4142 app step already retired seven dead keys as retiredKey tombstones — they stay in the walked shape, so their rows stay here (tombstone rule, orphans.mts). WALK BOUNDARY (#3095 union rule): `navigation` drills into the union's FIRST member (the `object` variant + base keys); the other variants' payload keys sit outside the walk and were verified by hand — dashboardName (NavigationRenderer.tsx:433), pageName (:435-442), url/target (:462), reportName (:460), componentRef (:464,:644), group `expanded` (:856) all live. The one GAP found there is now CLOSED (#4509, objectui @e8bec83): an `action` item's click dispatches through a host-supplied `onAction` prop that no shipped shell passed, so `actionDef.actionName` reached no dispatcher and every such item dead-clicked. objectui's `useNavActionDispatch` (objectui: packages/app-shell/src/hooks/useNavActionDispatch.ts) resolves the name against `action` metadata and dispatches through the console action runtime, and UnifiedSidebar passes it (objectui: packages/app-shell/src/layout/UnifiedSidebar.tsx:473). A shell that still passes no handler now HIDES action items rather than rendering them dead (objectui: packages/layout/src/NavigationRenderer.tsx:971) — the renderer stops manufacturing the trap. Also note that filterAppForUser USED to walk only the top-level `navigation` tree — it never read `item.areas` at all (it returned early when `navigation` was absent), while the client area switcher renders every area. That made area-level `visible` / `requiredPermissions` FAIL-OPEN gates, not merely unread: a \"hidden\" or permission-gated area showed to everyone. Closed in #4722 for the layer that survived the retirement — the server now runs the same filterNav over every `areas[].navigation` — see the `areas.navigation` row below. AREA GATES, 17.0.0 (#4651): both keys REMOVED and their rows DELETED — NavigationAreaSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Route B (remove) over route A (enforce) was the maintainer's call: enforcing needs semantics decided first (does filtering an area remove its items everywhere? does the server bind `user` for area CEL?), which the 17.0.0 window could not hold, and a gate that never gated is strictly safer removed than shipped for a whole major. The strict rejection carries the prescription (ui/app.zod.ts AREA_VISIBLE_RETIRED / AREA_REQUIRED_PERMISSIONS_RETIRED) and names the layers that DO enforce. The boundary those prescriptions pointed at — per-item gating inside an area being shell-side only — was the real gap #4651 left behind, and #4722 closed it: item-level `requiredPermissions` / `requiresService` are now stripped server-side inside `areas[]` too, `visible` (CEL) deliberately not. Recorded on `areas.navigation` below. The area-LEVEL keys remain retired; they were not revived. Seeded 2026-08-01 (#4488). CONTEXT SELECTORS, 17.0.0 (#4509): `includeAll` and `placement` rows DELETED — AppContextSelectorSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Both were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied), which made removal the only channel that could reach an author. `includeAll` was the sharp one: not unread but deliberately DISOBEYED — selectors are mandatory-scope, and an All row would clear the scope, which on Studio's package selector means listing the platform's own system/cloud kernel packages. STUDIO_APP authored `includeAll: true` against a renderer that ignored it, and that authoring site went with the key.",
"props": {
"name": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/spec/src/system/i18n-resolver.ts:478",
"note": "routing identity (`/apps/<name>`) and the translation-bundle key (`apps.<name>.*`); objectui RootLandingRedirect routes by it."
},
"label": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/spec/src/system/i18n-resolver.ts:481; objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:327-355 (switcher/header)",
"note": "localized on serve by translateApp, rendered by the app switcher and shell header."
},
"description": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:333; framework packages/spec/src/system/i18n-resolver.ts:482",
"note": "rendered under the active app's title; localized by translateApp."
},
"icon": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:327, :355",
"note": "App Launcher / switcher icon."
},
"branding": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:185-186 (logo, primaryColor); objectui packages/app-shell/src/layout/ConsoleLayout.tsx:172-173 (accentColor, favicon)",
"note": "all four children live in the shell chrome; `accentColor` and the `separator`/`badgeVariant` nav keys were themselves inverse-drift fixes (declared to match an existing objectui read, liveness audit #1878/#1891/#1894)."
},
"active": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:178",
"note": "`active: false` delists the app from the switcher. Deliberately does NOT disable routing — the active-app lookup spans all apps so a direct /apps/<name> URL keeps rendering (AppSidebar:179-180 comment). Weaker than the name implies, but a real consumer."
},
"isDefault": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: apps/console/src/components/RootLandingRedirect.tsx:46",
"note": "ROUTING semantics: the root landing redirects to the app marked default (it was once a display-only badge — the file says so)."
},
"hidden": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/rest/src/rest-server.ts:1811; objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:178",
"note": "SERVER-enforced (ADR-0045): a hidden app is served only to builders (studio/setup access) for direct-URL preview; the client switcher filter is a listing courtesy on top."
},
"navigation": {
"children": {
"id": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:125",
"note": "item identity: render keys, pin/reorder persistence, i18n nav key (`apps.<app>.navigation.<id>.label`)."
},
"label": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/spec/src/system/i18n-resolver.ts:456; objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:284-316 (resolveNavItemLabel)",
"note": "rendered everywhere; translateApp swaps in the per-locale label by node id."
},
"icon": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:961",
"note": "every variant branch resolves and renders it."
},
"order": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:1154, :913",
"note": "low-first stable sort at the top level and inside each group."
},
"badge": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:983-985"
},
"badgeVariant": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:983, :1024",
"note": "declared to match this exact read (inverse-drift fix, audit #1878/#1891/#1894)."
},
"visible": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:891 (item gate); objectui packages/app-shell/src/layout/AppSidebar.tsx:236 (CEL evaluation via ExpressionProvider)",
"note": "the CEL visibility gate — enforced per item. This is the layer the retired `areas[].visible` (#4651) prescribes moving to: same CEL dialect, one level down, and actually evaluated. CLIENT-side only, at both levels, and deliberately so: #4722 made the server enforce `requiredPermissions` / `requiresService` inside `areas[]` but left CEL where it was, because evaluating it server-side needs a bound `user` context the REST read layer does not have. Anything that must never reach the browser belongs in `requiredPermissions`, not `visible` — the asymmetry is pinned in packages/rest/src/rest.test.ts."
},
"requiredPermissions": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/rest/src/rest-server.ts:1844; objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:894",
"note": "enforced in BOTH layers: the server strips unsatisfied entries before serving — from the top-level navigation tree AND, since #4722, from every `areas[].navigation` tree through the same filterNav — and the client re-gates per item."
},
"requiresObject": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:899-901",
"note": "runtime-capability gate against the SchemaRegistry (client-side; the server gates only requiresService)."
},
"requiresService": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/rest/src/rest-server.ts:1832; objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:900-902",
"note": "ADR-0057 D10 capability gate, server + client."
},
"type": {
"status": "live",
"verifiedAt": "2026-08-02",
"evidence": "objectui @e8bec83: packages/layout/src/NavigationRenderer.tsx:905-1020 (branch dispatch), :397-471 (href resolution per variant), :963-971 (action branch — hidden when the host passes no dispatcher)",
"note": "the discriminant. Variant payload keys outside this walk are covered in the type note — all live, the `actionDef` dispatch gap included since #4509 wired it to the console action runtime."
},
"objectName": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:296, :397-418"
},
"viewName": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:294-296, :418",
"note": "target precedence recordId → filters → viewName; also keys the view-label i18n lookup."
},
"recordId": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:397-406, :598",
"note": "direct-to-record deep link; {current_user_id}/{current_org_id} and context-selector {<id>} template vars substituted by the shell."
},
"recordMode": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:406"
},
"filters": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:416-418",
"note": "serialized as filter[<field>]=<value> params onto the bare /data surface (objectui ADR-0055); exclusivity with recordId/viewName is parse-rejected (objectNavTargetExclusivity)."
},
"children": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:910-913",
"note": "recursive render with per-group order sort; the server's filterNav collapses groups emptied by permission stripping (rest-server.ts:1837)."
}
},
"note": "Walked children are the `object` variant + base keys (union-first rule) — see the type note for the other variants' hand-verified payload keys."
},
"areas": {
"children": {
"id": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:197-210",
"note": "area-switcher identity and active-area state key."
},
"label": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:456"
},
"icon": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:448"
},
"description": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "display annotation no surface renders. Benign — docs-shaped, kept, not warned (hook.label precedent)."
},
"navigation": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:210, packages/layout/src/AppSchemaRenderer.tsx:469",
"note": "the active area's tree replaces the top-level navigation. Since #4722 area trees ARE server-side gated: filterAppForUser (packages/rest/src/rest-server.ts:1870) runs the SAME filterNav over every `areas[].navigation`, so an item's `requiredPermissions` / `requiresService` is enforced identically in both trees and a gated entry (with its objectName/pageName/componentRef target) never reaches the browser. An area emptied BY the gate is dropped, mirroring the top-level group collapse; an area authored empty is passed through. Still client-only at both levels: `visible` (CEL — needs a bound user context the read layer lacks) and `requiresObject`. The area-LEVEL keys stay retired (#4651) — this enforces the items inside, not a revived area gate."
}
},
"note": "Drilled because the gating keys diverged sharply from the live identity/tree keys — and they are gone: `visible` and `requiredPermissions` were RETIRED in 17.0.0 (#4651), rows DELETED because NavigationAreaSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Keep drilling: `description` is the surviving benign dead key, and the drill is what would catch a new gate being added here."
},
"contextSelectors": {
"children": {
"id": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:199",
"note": "also the nav template-variable name ({<id>} substitution into recordId/params)."
},
"label": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:232"
},
"icon": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:231"
},
"optionsSource": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:90-125",
"note": "endpoint fetched, valueKey/labelKey dotted-path mapped, `filter` predicates applied per row (rowPasses, :69-71)."
},
"allValue": {
"status": "live",
"verifiedAt": "2026-08-02",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:177, :199, :246",
"note": "RE-VERIFIED 2026-08-02 (#4509) — the re-check the previous note asked for, now that includeAll is gone. Still live, and the reading is unchanged: it is the 'nothing concrete selected' sentinel for auto-selection and query-param defaulting. What changed is the DECLARATION: the describe() no longer calls it 'the value emitted when All is selected', because that event cannot occur and never could — selectors are mandatory-scope and the renderer never offered an All row. Live by the letter AND by the letter now; the vestigial-by-intent caveat retired with includeAll."
},
"persist": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/layout/ContextSelectors.tsx:164",
"note": "'none' opts out of persistence; query/session honored."
}
},
"note": "Drilled because includeAll/placement diverge (dead) from the live core."
},
"homePageId": {
"status": "dead",
"verifiedAt": "2026-08-02",
"note": "REMOVED 2026-08-02 (#4667) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and stripped from sources by the protocol-17 conversion. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent). PREMISE CORRECTED 2026-08-03 (#4709), removal upheld: #4667 justified this row with \"no shell ever read it\", which was FALSE and contradicted this repo's own 2026-06 AppSchema liveness audit (docs/audits/2026-06-appschema-property-liveness.md), which listed homePageId LIVE. objectui's console DID read it — resolveLandingRoute(), packages/app-shell/src/console/AppContent.tsx @785b8a5d — and it was the only thing deciding where an app opened. The key is retired for its SHAPE: an ID cross-reference into navigation with no referential integrity, silently falling back to the first item when it dangled. Post-v17 the landing IS the first navigation item (by order), and the ROOT landing follows isDefault routing (objectui RootLandingRedirect). Reorder navigation, or set isDefault, instead; if the capability returns it belongs on the navigation item itself (navigation[].landing), enforce-first. The three aliases that routed here (home / homepage / landingpage) were removed from AppSchema aliases and now carry the same prescription as guidance. Process lesson: a retirement citing liveness MUST reconcile against existing audit records — this contradiction went unnoticed for two months."
},
"requiredPermissions": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "packages/rest/src/rest-server.ts:1814",
"note": "SERVER-enforced: an app whose required permissions are not a subset of the caller's system permissions is dropped from /meta entirely (and the single-item GET re-checks at rest-server.ts:3298)."
},
"defaultAgent": {
"status": "live",
"verifiedAt": "2026-08-01",
"evidence": "objectui @940ba24: packages/app-shell/src/hooks/surfaceAgent.ts:81, packages/app-shell/src/layout/ChatDock.tsx:253",
"note": "bounded surface-binding knob (ADR-0063): resolved to the two platform agents (ask/build), alias-aware, anything else rejected — exactly as the schema documents."
},
"version": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142, 2026-06 audit) — authoring it is parse-rejected with the prescription (an app is versioned by its package's manifest.version). Row stays while the tombstone keeps the key in the walked shape."
},
"aria": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142) — app-level ARIA was never read; declare aria on the rendering component/widget."
},
"objects": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142) — objects belong to the stack; the ambient chatbot derives an app's object list from its nav items (collectNavObjects), never from App.objects."
},
"apis": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142) — declarative endpoints belong to the stack."
},
"sharing": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142, ADR-0049) — a declared-but-unenforced security surface; the live sharing path is FormView.sharing."
},
"embed": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142, ADR-0049) — no iframe route ever read it; embedding is per form view."
},
"mobileNavigation": {
"status": "dead",
"verifiedAt": "2026-08-01",
"note": "retiredKey tombstone (#4142) — fully unimplemented; returns if/when a real mobile navigation ships."
}
}
}