@sigrun/shared 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/access.d.ts +60 -0
  3. package/dist/access.js +87 -0
  4. package/dist/ambient.d.ts +54 -0
  5. package/dist/ambient.js +25 -0
  6. package/dist/blocks/format.d.ts +28 -0
  7. package/dist/blocks/format.js +199 -0
  8. package/dist/blocks/icons.d.ts +12 -0
  9. package/dist/blocks/icons.js +64 -0
  10. package/dist/blocks/index.d.ts +25 -0
  11. package/dist/blocks/index.js +21 -0
  12. package/dist/blocks/instance.d.ts +29 -0
  13. package/dist/blocks/instance.js +51 -0
  14. package/dist/blocks/lttb.d.ts +16 -0
  15. package/dist/blocks/lttb.js +67 -0
  16. package/dist/blocks/ops.d.ts +44 -0
  17. package/dist/blocks/ops.js +294 -0
  18. package/dist/blocks/text.d.ts +43 -0
  19. package/dist/blocks/text.js +804 -0
  20. package/dist/blocks/transport.d.ts +81 -0
  21. package/dist/blocks/transport.js +11 -0
  22. package/dist/blocks/types.d.ts +558 -0
  23. package/dist/blocks/types.js +17 -0
  24. package/dist/commands.d.ts +75 -0
  25. package/dist/commands.js +25 -0
  26. package/dist/conversations.d.ts +31 -0
  27. package/dist/conversations.js +94 -0
  28. package/dist/interactions.d.ts +65 -0
  29. package/dist/interactions.js +23 -0
  30. package/dist/jobs.d.ts +109 -0
  31. package/dist/jobs.js +19 -0
  32. package/dist/json.d.ts +20 -0
  33. package/dist/json.js +13 -0
  34. package/dist/live.d.ts +71 -0
  35. package/dist/live.js +82 -0
  36. package/dist/session-meta.d.ts +130 -0
  37. package/dist/session-meta.js +13 -0
  38. package/dist/settings.d.ts +449 -0
  39. package/dist/settings.js +19 -0
  40. package/dist/themes.d.ts +49 -0
  41. package/dist/themes.js +155 -0
  42. package/dist/turns.d.ts +133 -0
  43. package/dist/turns.js +247 -0
  44. package/dist/usernames.d.ts +8 -0
  45. package/dist/usernames.js +39 -0
  46. package/dist/view-delta.d.ts +54 -0
  47. package/dist/view-delta.js +135 -0
  48. package/dist/views.d.ts +1021 -0
  49. package/dist/views.js +54 -0
  50. package/package.json +3 -2
@@ -0,0 +1,1021 @@
1
+ /**
2
+ * §5 wire types (Tasks 9a–9d) — THE channel contract.
3
+ *
4
+ * Every HTTP/SSE payload shape both clients (webui, tui) consume is
5
+ * declared or re-exported here, and both import it directly (the tui
6
+ * imports from `@sigrun/shared`, never from server source). It is the
7
+ * intended basis for a generated OpenAPI schema later, so the purity
8
+ * rules are enforced by `test/views-purity.test.ts`:
9
+ *
10
+ * - imports and re-exports are `import type`/`export type` from SIBLING
11
+ * shared modules only, and type-only — they erase at compile time and
12
+ * contribute no runtime (the only runtime exports here are local pure
13
+ * constants);
14
+ * - no external package imports, no server/framework types, no route
15
+ * logic (the server's `src/server/views.ts` owns the RUNTIME
16
+ * serializer and re-exports this module);
17
+ * - no `node:` imports (the webui compiles DOM-only against this file).
18
+ *
19
+ * Spec §5: the server serves pi-durable's NATIVE views, not a bespoke
20
+ * endpoint list — so the conversation wire payload IS the view shape
21
+ * (`{ conversation, entries, docs }`; JSON-safe by construction) PLUS
22
+ * the sigrun-side `steer` field. The upstream view's inner shapes are
23
+ * re-declared STRUCTURALLY below (`ViewConversation`, `ViewEntry`,
24
+ * `ViewMessage`, `WireBlock`): deliberately loose where channels only
25
+ * read a subset, and structurally compatible with the upstream types so
26
+ * the server's serializer (which spreads an upstream
27
+ * `ConversationView`) typechecks against this declaration — the
28
+ * captured-schema fixture (`src/server/schema-fixture.ts`,
29
+ * `view-schema.json`) pins the UPSTREAM shape, and
30
+ * `test/schema-drift.test.ts` trips on upstream drift.
31
+ *
32
+ * `serializeConversationView` (src/server/views.ts) is the single
33
+ * chokepoint every §5 endpoint that returns a conversation goes through
34
+ * — including each SSE watch frame (T9d) — so the fixture continues to
35
+ * describe the wire.
36
+ */
37
+ import type { JsonObject } from "./json.js";
38
+ import type { Sharing } from "./access.js";
39
+ import type { JobWire } from "./jobs.js";
40
+ import type { InteractionAnswer, InteractionRecord, InteractionStatus } from "./interactions.js";
41
+ import type { AmbientRecord } from "./ambient.js";
42
+ import type { EffectiveServerSetting, SettingsFile } from "./settings.js";
43
+ /** A model reference as served on the wire (pi-durable `ModelRef`). */
44
+ export interface ModelRefWire {
45
+ readonly provider: string;
46
+ readonly modelId: string;
47
+ }
48
+ /** A model's thinking level (pi-ai `ModelThinkingLevel` union). */
49
+ export type ThinkingLevelWire = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
50
+ /** One content block of a message (`{type: "text", text}`, `{type:
51
+ * "thinking", thinking}`, image/file parts, tool calls) — the loose
52
+ * block shape every renderer dispatches on. */
53
+ export interface WireBlock {
54
+ readonly type: string;
55
+ readonly text?: string;
56
+ /** The thinking text on `{type: "thinking"}` blocks. */
57
+ readonly thinking?: string;
58
+ /** Tool-call identity on `{type: "toolCall"}` blocks. */
59
+ readonly id?: string;
60
+ readonly name?: string;
61
+ readonly arguments?: unknown;
62
+ }
63
+ /** One model message carried by a view entry (pi-ai JSON shape). */
64
+ export interface ViewMessage {
65
+ readonly role: string;
66
+ readonly content: string | readonly WireBlock[];
67
+ /** Tool correlation — present on `toolResult`-role messages. */
68
+ readonly toolName?: string;
69
+ readonly toolCallId?: string;
70
+ readonly isError?: boolean;
71
+ }
72
+ /** One entry of the view's entry list. */
73
+ export interface ViewEntry {
74
+ readonly id: number;
75
+ readonly conversationId?: number;
76
+ /** Application-defined entry discriminator (`pi.user`, `pi.assistant`, …). */
77
+ readonly kind: string;
78
+ /** Messages contributed to model context; absent on display/bookkeeping entries. */
79
+ readonly model?: readonly ViewMessage[];
80
+ /** The entry's kind-specific JSON payload, when it carries one. */
81
+ readonly data?: unknown;
82
+ }
83
+ /** One conversation as pi-durable serves it (the upstream view). */
84
+ export interface ViewConversation {
85
+ readonly id: number;
86
+ /** Fork lineage (the upstream `parent`), when this conversation is a fork. */
87
+ readonly parent?: {
88
+ readonly conversationId?: number;
89
+ readonly at?: number;
90
+ };
91
+ /** Creator edge, when the conversation was produced by durable work. */
92
+ readonly owner?: unknown;
93
+ }
94
+ /** The upstream view, as the channel reads it. */
95
+ export interface ConversationViewBase {
96
+ readonly conversation: ViewConversation;
97
+ readonly entries: readonly ViewEntry[];
98
+ /** Built-in conversation documents keyed by kind; absent documents are absent. */
99
+ readonly docs: Readonly<Record<string, JsonObject>>;
100
+ }
101
+ /**
102
+ * The per-conversation steer field (spec §5: "reflecting submit-while-busy
103
+ * queue semantics"). `busy` = a run is live; `queued` = submissions
104
+ * waiting in the inbox behind it.
105
+ */
106
+ export interface SteerField {
107
+ readonly busy: boolean;
108
+ readonly queued: number;
109
+ }
110
+ /**
111
+ * One conversation as served over the wire: the upstream view plus the
112
+ * derived `steer` field (see the server's
113
+ * `serializeConversationView` — every §5 endpoint that returns a
114
+ * conversation, including each SSE watch frame, goes through it).
115
+ */
116
+ export type ConversationViewWire = ConversationViewBase & {
117
+ readonly steer: SteerField;
118
+ };
119
+ /** One list item of `GET /v1/conversations` (Task 9a: ids, not full views). */
120
+ export interface ConversationSummary {
121
+ readonly id: number;
122
+ /** The conversation this summary describes is the reserved root. */
123
+ readonly root: boolean;
124
+ /**
125
+ * The conversation's session-meta owner, when stamped (Task 2's
126
+ * `X-Sigrun-User` seam — Task 3 surfaced it here so a picker can show
127
+ * who owns what). Absent when the doc never carried one.
128
+ */
129
+ readonly owner?: string;
130
+ /**
131
+ * The display name: the user's `meta.name`, else the server-owned
132
+ * `meta.autoName` (A2's auto-namer). Absent when neither was written.
133
+ */
134
+ readonly name?: string;
135
+ /**
136
+ * The server-owned auto-generated name (A2), when written and no user
137
+ * `name` overrides it. Surfaced so `conversation.changed` events can
138
+ * report an auto-name landing without a user edit.
139
+ */
140
+ readonly autoName?: string;
141
+ /**
142
+ * The session-meta source — who created the session (spec §9.5):
143
+ * `"user"` for HTTP creates, the extension slug for channel-bridge
144
+ * creates through `host.conversations`. Absent when the doc was never
145
+ * written (defaults are free).
146
+ */
147
+ readonly source?: string;
148
+ /**
149
+ * The last activity instant, ISO 8601 (activity.ts stamps it at
150
+ * submission admission and run settlement). Absent on a conversation
151
+ * that was never used.
152
+ */
153
+ readonly lastActivityAt?: string;
154
+ /**
155
+ * Fork lineage: the source conversation id and the entry the history
156
+ * was forked at, when this conversation is a fork. Absent on a root
157
+ * or plainly created conversation.
158
+ */
159
+ readonly parent?: {
160
+ readonly id: number;
161
+ readonly at: number;
162
+ };
163
+ /** The session-meta archive flag (the sidebar's hide), default false. */
164
+ readonly archived: boolean;
165
+ /** The session-meta pin flag (pinned conversations float to top), default false. */
166
+ readonly pinned: boolean;
167
+ /**
168
+ * The §5.9 sharing state, when the conversation carries one (absent =
169
+ * private). Surfaced so a picker can show what is shared.
170
+ */
171
+ readonly sharing?: Sharing;
172
+ /**
173
+ * THIS caller's resolved access to the conversation (§5.9 `accessOf`):
174
+ * conversations the caller cannot see are filtered from the list
175
+ * entirely, so every listed row is at least `read`.
176
+ */
177
+ readonly access: "read" | "write";
178
+ }
179
+ /** The list response of `GET /v1/conversations`. */
180
+ export interface ConversationList {
181
+ readonly conversations: readonly ConversationSummary[];
182
+ }
183
+ /** The create response of `POST /v1/conversations`. */
184
+ export interface ConversationCreateResponse {
185
+ readonly id: number;
186
+ readonly view: ConversationViewWire;
187
+ }
188
+ /** `GET /v1/jobs`: the caller-visible jobs (`accessOf ≠ "none"`), each row
189
+ * carrying the caller's resolved `access`. */
190
+ export interface JobsResponse {
191
+ readonly jobs: readonly JobWire[];
192
+ }
193
+ /** `POST /v1/jobs/:id/run`: the fire outcome plus the run's conversation
194
+ * when one exists (a skipped/blocked fire may have none). */
195
+ export interface JobRunResponse {
196
+ readonly outcome: "running" | "skipped" | "blocked";
197
+ readonly conversationId?: number;
198
+ }
199
+ /** The `Content-Type` of the JSONL export/import wire (Task 5b/5c). */
200
+ export declare const JSONL_CONTENT_TYPE = "application/x-ndjson";
201
+ /**
202
+ * The fork response of `POST /v1/conversations/:id/fork` (Task 5a): the
203
+ * create shape — a NEW conversation id plus its first view frame.
204
+ */
205
+ export type ConversationForkResponse = ConversationCreateResponse;
206
+ /**
207
+ * The import response of `POST /v1/conversations/import` (Task 5c): the
208
+ * create shape — the new conversation id plus its view with the appended
209
+ * entries in order.
210
+ */
211
+ export type ConversationImportResponse = ConversationCreateResponse;
212
+ /**
213
+ * The durable submission record on the wire (§7.1): the record as it
214
+ * stands when read — `status` is `queued` | `placed` | `done` |
215
+ * `unanswered`; `entry`/`answer`/`reason`/`detail` appear per status.
216
+ */
217
+ export interface SubmissionRecordWire {
218
+ readonly id: number;
219
+ readonly conversationId: number;
220
+ /** Host-provided deduplication key, scoped to the conversation. */
221
+ readonly requestId?: string;
222
+ readonly type: string;
223
+ readonly status: string;
224
+ readonly entry?: number;
225
+ readonly answer?: number;
226
+ readonly reason?: string;
227
+ readonly detail?: unknown;
228
+ }
229
+ /**
230
+ * The admit response of `POST /v1/conversations/:id/submissions` (§7.1):
231
+ * `202 {submission}` AT ADMISSION — the record as it stands the moment
232
+ * the submission is admitted. `?waitMs=<n>` (a positive int ≤ 60 000)
233
+ * opts into a bounded settlement wait; `GET …/submissions/:submissionId`
234
+ * polls the record later.
235
+ */
236
+ export interface SubmissionResponse {
237
+ readonly submission: SubmissionRecordWire;
238
+ }
239
+ /** The upload response of `POST /v1/conversations/:id/uploads` (Task 9b). */
240
+ export interface UploadResponse {
241
+ /** The workspace-relative path to use in submission content parts. */
242
+ readonly path: string;
243
+ }
244
+ /** One `GET /v1/conversations/:id/files` entry (upstream `FileInfo`). */
245
+ export interface FileEntry {
246
+ readonly name: string;
247
+ readonly path: string;
248
+ readonly kind: "file" | "directory" | "symlink";
249
+ readonly size: number;
250
+ readonly mtimeMs: number;
251
+ }
252
+ /** The list response of `GET /v1/conversations/:id/files` (Task 9b). */
253
+ export interface FileListResponse {
254
+ readonly entries: readonly FileEntry[];
255
+ }
256
+ /** The read response of `GET /v1/conversations/:id/files/content`. */
257
+ export interface FileContentResponse {
258
+ /** `"text"` (valid UTF-8) or `"base64"` (binary). */
259
+ readonly encoding: "text" | "base64";
260
+ readonly content: string;
261
+ /** Size in bytes. */
262
+ readonly size: number;
263
+ }
264
+ /** One `GET /v1/models` entry (Task 9b): picker fields, no provider internals. */
265
+ export interface ModelEntry {
266
+ /** `"provider/modelId"` — the id submission/configure `model` takes. */
267
+ readonly id: string;
268
+ readonly provider: string;
269
+ readonly modelId: string;
270
+ readonly name: string;
271
+ readonly contextWindow: number;
272
+ readonly maxTokens: number;
273
+ readonly reasoning: boolean;
274
+ readonly input: readonly string[];
275
+ readonly cost: {
276
+ readonly input: number;
277
+ readonly output: number;
278
+ readonly cacheRead: number;
279
+ readonly cacheWrite: number;
280
+ };
281
+ }
282
+ /** The response of `GET /v1/models`. */
283
+ export interface ModelsResponse {
284
+ readonly models: readonly ModelEntry[];
285
+ }
286
+ /** The configured agent surface `POST .../configure` returns (T9c-i). */
287
+ export interface ConfigureResponse {
288
+ /** Absent when unset — no model means no generation. */
289
+ readonly model?: ModelRefWire;
290
+ readonly thinkingLevel: ThinkingLevelWire;
291
+ }
292
+ /** The result mapping `POST .../abort` returns (T9c-i). */
293
+ export type AbortResponse = {
294
+ readonly result: "aborted";
295
+ readonly submissionId: number;
296
+ } | {
297
+ readonly result: "already_placed";
298
+ readonly submissionId: number;
299
+ } | {
300
+ readonly result: "settled";
301
+ readonly submissionId: number;
302
+ };
303
+ /**
304
+ * The admit response of `POST /v1/conversations/:id/compact` (Task 4, pi
305
+ * parity): the durable compaction task's id. The task is conversation-owned
306
+ * (manual): it compacts even when idle and places its summary through a
307
+ * write submission; the SSE stream carries the `pi.compaction` entry.
308
+ */
309
+ export interface CompactResponse {
310
+ readonly taskId: number;
311
+ }
312
+ /** One interaction record on the wire (the envelope + body + details). */
313
+ export type InteractionWire = InteractionRecord;
314
+ /** The response of `GET /v1/conversations/:id/interactions`. */
315
+ export interface InteractionList {
316
+ readonly interactions: readonly InteractionWire[];
317
+ }
318
+ /**
319
+ * One pending interaction in the cross-conversation inbox (§7.5): the
320
+ * record plus the conversation it waits in, so a client can answer
321
+ * without opening each conversation (answers still go through the
322
+ * per-conversation answer route).
323
+ */
324
+ export type InboxInteraction = InteractionWire & {
325
+ readonly conversationId: number;
326
+ };
327
+ /**
328
+ * The response of `GET /v1/interactions?status=pending` (§7.5): every
329
+ * pending interaction in a conversation the caller can see, sorted by
330
+ * `createdAt` ascending. `pending` is the only indexed (and accepted)
331
+ * status.
332
+ */
333
+ export interface InboxResponse {
334
+ readonly interactions: readonly InboxInteraction[];
335
+ }
336
+ /** The response of `POST .../interactions/:callId/answer`. */
337
+ export interface InteractionAnswered {
338
+ readonly callId: string;
339
+ readonly status: "answered";
340
+ readonly answer: InteractionAnswer;
341
+ }
342
+ /** One ambient surface on the wire (envelope + body + details). */
343
+ export type AmbientWire = AmbientRecord;
344
+ /** The response of `GET /v1/conversations/:id/ambient`. */
345
+ export interface AmbientList {
346
+ readonly ambient: readonly AmbientWire[];
347
+ }
348
+ export type { InteractionAnswer, InteractionStatus, };
349
+ /**
350
+ * The `GET/PATCH /v1/settings` payload (Task 8b): the settings file's
351
+ * effective contents — the `HarnessSettings` pass-throughs plus the
352
+ * sigrun-resolved fields — plus `toolRoster`, the registry-driven roster
353
+ * the settings panel renders `defaultTools` over (Task 3, codemode
354
+ * parity). Absent file = every default; the roster is SERVER-COMPUTED
355
+ * from the live registry snapshot per request (never persisted, never
356
+ * snapshotted at boot), so extension tools installed later — codemode,
357
+ * tool_search — appear without panel changes. `toolRoster` is NOT a
358
+ * settable key: the store's validation rejects it on PATCH (400
359
+ * `invalid_field`), and the wire patch type Omits it.
360
+ */
361
+ export interface SettingsResponse extends SettingsFile {
362
+ /** The live registry snapshot's composed tool names, deduplicated. */
363
+ readonly toolRoster: readonly string[];
364
+ /**
365
+ * The restart keys whose stored value differs from the boot snapshot
366
+ * (UI-blocks U4, spec §13.2): the pending-restart callout's key list.
367
+ * Computed per request over the fresh file read; `[]` when nothing
368
+ * waits for a restart. NOT a settable key: the store's validation
369
+ * rejects it on PATCH (400 `invalid_field`), and the wire patch type
370
+ * Omits it.
371
+ */
372
+ readonly pendingRestart: readonly string[];
373
+ /**
374
+ * The deployment-managed top-level settings keys. Read fresh from the
375
+ * seeder's managed-settings.json on every request; a missing file means
376
+ * nothing is managed. NOT a settable key: the store's validation rejects
377
+ * it on PATCH (400 `invalid_field`), and the wire patch type Omits it.
378
+ */
379
+ readonly managed: readonly string[];
380
+ /**
381
+ * The write's own classification (§13.1) — present ONLY on a PATCH
382
+ * response: which touched keys applied live vs. which are pending a
383
+ * restart. NOT a settable key; absent on GET.
384
+ */
385
+ readonly applied?: readonly string[];
386
+ /** The restart-pending companion of {@link applied}. PATCH responses only. */
387
+ readonly pending?: readonly string[];
388
+ /**
389
+ * The resolved server section per flattened dotted key
390
+ * (`"server.port"`, `"server.compaction.reserveTokens"`, …), each
391
+ * with its resolved value and which tier supplied it — `"env"`,
392
+ * `"file"`, or `"default"` (spec §6.6: the generalization of the
393
+ * build spec's webui-key special case; since the bootstrap persists
394
+ * only env-set values, this block is where the full resolved section
395
+ * is visible). Computed per request, never persisted. NOT a settable
396
+ * key: the store's validation rejects it on PATCH (400
397
+ * `invalid_field`), and the wire patch type Omits it.
398
+ */
399
+ readonly effective: Readonly<Record<string, EffectiveServerSetting>>;
400
+ }
401
+ /** Spec §3 health status: ok unless the surface knows it is degraded. */
402
+ export type HealthStatus = "ok" | "degraded";
403
+ /**
404
+ * One idle worker an extension declared (memory-seam Task 5): the bus's
405
+ * per-worker observability record as `/v1/meta` serves it.
406
+ */
407
+ export type WorkerStatus = {
408
+ readonly name: string;
409
+ readonly lastRunAt?: number;
410
+ readonly lastError?: string;
411
+ };
412
+ /** The `GET /v1/meta` payload (spec §5; the health block is Task 9e). */
413
+ /**
414
+ * The measured listener facts (release-readiness Task 1, serving spec §2):
415
+ * captured ONCE when the listener goes live (never probed per request) and
416
+ * retained on the server context; `GET /v1/meta` projects them verbatim.
417
+ */
418
+ export interface ListenerInfo {
419
+ /** The effective host string supplied to `listen` — never a resolved peer. */
420
+ readonly host: string;
421
+ /** The configured/selected port the listener bound. */
422
+ readonly port: number;
423
+ /**
424
+ * The address families the listener accepts, in stable `ipv4`, `ipv6`
425
+ * order. Literal binds report the bound socket's family; the `::`
426
+ * wildcard reports exactly the loopback families a startup reachability
427
+ * check connected on (measured, never inferred from the wildcard
428
+ * spelling). Empty on a server that never listened (a hand-built test
429
+ * server projecting intent only).
430
+ */
431
+ readonly families: readonly ("ipv4" | "ipv6")[];
432
+ }
433
+ export interface MetaResponse {
434
+ /** The sigrun server's own version (the package manifest). */
435
+ readonly version: string;
436
+ readonly harnessVersion: string;
437
+ readonly schemaVersion: string;
438
+ /**
439
+ * The listener facts (release-readiness Task 1, serving spec §2). A
440
+ * REQUIRED field on this type; OLDER SERVERS (≤0.2.0) omit it on the
441
+ * wire, so clients consuming it must tolerate its absence during
442
+ * mixed-version upgrades.
443
+ */
444
+ readonly listener: ListenerInfo;
445
+ readonly extensions: readonly {
446
+ readonly name: string;
447
+ readonly status: string;
448
+ readonly error?: string;
449
+ /**
450
+ * The extension's idle workers (memory-seam Task 5), when it
451
+ * declared any; `lastRunAt`/`lastError` stay absent until the
452
+ * worker's first run.
453
+ */
454
+ readonly workers?: readonly WorkerStatus[];
455
+ /**
456
+ * The declared surfaces (UI-blocks U3 Task 5): the flattened
457
+ * view + panel rows the channels register their rail/panel
458
+ * entries from. Absent when the extension declared none. The
459
+ * meta response carries the DECLARATION (id/title/icon/
460
+ * placement); access filtering is render-time (§6.5).
461
+ */
462
+ readonly surfaces?: readonly ExtensionSurfaceInfo[];
463
+ }[];
464
+ /**
465
+ * §5 health: pure aggregation over boot-retained §3 producers (spec
466
+ * §3) — synchronous status reads, never network. The one deliberate
467
+ * exception is `backup` (§6.8): its §6.8 derivation reads the small
468
+ * `backup/last-success.json` marker from the state root per request
469
+ * (the CronJob is a separate process; the filesystem is the only
470
+ * fresh-enough view).
471
+ */
472
+ readonly health: {
473
+ /**
474
+ * §6.8 backup honesty: presence-derived when no target is
475
+ * configured ("unconfigured"); with a target, "configured" only
476
+ * while the last-success marker is present and younger than
477
+ * `server.backup.staleAfterHours` (default 36 h) — absent, corrupt,
478
+ * or stale all read "degraded".
479
+ */
480
+ readonly backup: "configured" | "unconfigured" | "degraded";
481
+ /** The memory extension's §3 producer, else "ok" (no-op default). */
482
+ readonly memory: HealthStatus;
483
+ /** Any contributor's §3 producer degraded → "degraded"; empty → "ok". */
484
+ readonly providers: HealthStatus;
485
+ /**
486
+ * The gateway engine's health (gateway C2): present ONLY when an
487
+ * engine loaded (a no-engine deploy reports NO gateway health —
488
+ * degenerate "ok" by absence, never a synthesized field); the
489
+ * wrapped engine health (a throwing `health()` reads "degraded";
490
+ * a rejected `start` latches degraded until restart).
491
+ */
492
+ readonly gateway?: HealthStatus;
493
+ };
494
+ /**
495
+ * The auth block (core hardening W1 Task 6, spec §5.7): the effective
496
+ * authenticator list (`ctx.methods` — loopback-derived or explicit) and
497
+ * the onboarding state (`claimed` = the user directory has its first
498
+ * user). A pure boot+store derivation, like the rest of meta — clients
499
+ * may cache it only per boot, since the claim flips it.
500
+ */
501
+ readonly auth: {
502
+ readonly methods: readonly ("token" | "proxy")[];
503
+ readonly claimed: boolean;
504
+ };
505
+ /**
506
+ * §6.8: the last successful backup's timestamp (the marker's `at`),
507
+ * surfaced whenever a readable marker exists against a configured
508
+ * target — including a STALE one (the degraded badge's "when was the
509
+ * last success"). Absent otherwise.
510
+ */
511
+ readonly backup: {
512
+ readonly lastSuccessAt?: string;
513
+ };
514
+ readonly capabilities: {
515
+ readonly backup: boolean;
516
+ readonly channels: readonly string[];
517
+ };
518
+ /**
519
+ * The development flag (spec §14.2/§14.4): `server.dev` / `SIGRUN_DEV`,
520
+ * default `false`. Gates the U1 blocks gallery, U3's development
521
+ * error bodies (validation errors in `surface_failed` responses) and
522
+ * U4 — a boot constant, like the rest of meta.
523
+ */
524
+ readonly dev: boolean;
525
+ /** The boot-time execution-jail decision (multi-user M1). */
526
+ readonly isolation: {
527
+ readonly mode: "auto" | "always" | "off";
528
+ /** Whether enforcement is active; filled by the later enforcement task. */
529
+ readonly active: boolean;
530
+ readonly available: boolean;
531
+ readonly abi: number;
532
+ readonly reason?: string;
533
+ readonly osUsers: "available" | "off" | string;
534
+ };
535
+ /**
536
+ * The supervised flag (UI-blocks U4 Task 2, spec §13.3): `true` when
537
+ * the deployment guarantees the process is restarted after an
538
+ * intentional `exit 0` (the Helm chart and docker compose set
539
+ * `SIGRUN_SUPERVISED=1`; a bare `docker run` does not). The §13.2
540
+ * pendingRestart callout renders its Restart button only for a
541
+ * supervised admin; otherwise it says "Restart sigrun to apply".
542
+ */
543
+ readonly supervised: boolean;
544
+ /**
545
+ * The seam inventory (docs/approval-ux-seams.md): per extension, the
546
+ * interaction kinds / ambient surface keys its factory declared.
547
+ * Extensions that declared nothing are absent — an absent entry means
548
+ * the extension writes no seam records, which is exactly the fail-open
549
+ * posture (channels render what is declared, nothing more).
550
+ */
551
+ readonly seam: {
552
+ readonly interactions: readonly {
553
+ readonly extension: string;
554
+ readonly kinds: readonly string[];
555
+ }[];
556
+ readonly ambient: readonly {
557
+ readonly extension: string;
558
+ readonly keys: readonly string[];
559
+ }[];
560
+ };
561
+ /**
562
+ * Boot-merge collisions (build-and-webui Task 6): name collisions among
563
+ * extension-contributed webui bundles, the provider-catalog merge sink,
564
+ * and extension COMMAND names (W5 Task 3). All three merges are
565
+ * last-registered-wins by design — the record is the operator's
566
+ * visibility into a co-installed replacement, never an error; empty
567
+ * arrays are the no-collision boot. A command colliding with a CORE
568
+ * name is DROPPED with a boot warning and deliberately NOT recorded
569
+ * here (core commands are reserved, not replaceable — there is no
570
+ * "winner" to report).
571
+ */
572
+ readonly collisions: {
573
+ readonly webui: readonly {
574
+ readonly kind: "webui-bundle";
575
+ /** The bundle name that collided. */
576
+ readonly name: string;
577
+ /** Extension slug that previously owned the name. */
578
+ readonly previous: string;
579
+ /** Extension slug whose contribution won. */
580
+ readonly contributor: string;
581
+ }[];
582
+ readonly providers: readonly {
583
+ readonly kind: "provider";
584
+ /** The provider id that collided. */
585
+ readonly provider: string;
586
+ /** Model ids present in both the previous and replacing catalog. */
587
+ readonly modelIds: readonly string[];
588
+ /** Contributor name that previously owned the provider. */
589
+ readonly previous: string;
590
+ /** Contributor name whose contribution won. */
591
+ readonly contributor: string;
592
+ }[];
593
+ readonly commands: readonly {
594
+ readonly kind: "command";
595
+ /** The command name that collided. */
596
+ readonly name: string;
597
+ /** Extension slug that previously owned the name. */
598
+ readonly previous: string;
599
+ /** Extension slug whose contribution won. */
600
+ readonly contributor: string;
601
+ }[];
602
+ };
603
+ }
604
+ /**
605
+ * Provider auth readiness statuses (Task 6, spec §6.4 + provider plugins
606
+ * Task 6): statuses only. The legacy vocabulary (`ready` | `not_ready` |
607
+ * `invalid`) is preserved VERBATIM for every derivation that exists today
608
+ * — the legacy producer mapping (tracker-less contributors) and the
609
+ * degenerate env-presence mapping — so old consumers keep working; the
610
+ * provider-plugins three-state vocabulary (`not_configured` |
611
+ * `degraded` | `ready`) is ADDED for tracker-backed providers, where
612
+ * `ready` is the shared spelling.
613
+ */
614
+ export type AuthStatus = "ready" | "not_ready" | "invalid" | "not_configured" | "degraded";
615
+ /**
616
+ * One `GET /v1/providers` entry — provider id + status, never key
617
+ * material. `detail` (provider plugins Task 6) is the optional
618
+ * tracker-facing operator detail; the legacy derivations never set it.
619
+ * Core hardening W3 Task 6 (spec §7.6) adds the catalog `models` count
620
+ * and the owning extension `owner` (absent for the env-degenerate
621
+ * provider) — the deprecated `/v1/auth/check` alias serves this same
622
+ * entry, one shape for both routes.
623
+ */
624
+ export interface AuthCheckProvider {
625
+ readonly provider: string;
626
+ readonly status: AuthStatus;
627
+ /** How many models the provider currently serves (best-effort count). */
628
+ readonly models: number;
629
+ /** The owning contributor's name; absent for the env-degenerate provider. */
630
+ readonly owner?: string;
631
+ readonly detail?: string;
632
+ }
633
+ /**
634
+ * The `GET /v1/providers` payload (core hardening W1 Task 6, spec
635
+ * §5.7/§7.6): the auth-check derivation under its new home — `/v1/auth`
636
+ * is API auth only, and the secret-free readiness report lives at
637
+ * `/v1/providers`. `GET /v1/auth/check` serves this same payload as a
638
+ * deprecated alias for one release (its responses carry a `Deprecation`
639
+ * header).
640
+ */
641
+ export interface ProvidersResponse {
642
+ readonly providers: readonly AuthCheckProvider[];
643
+ }
644
+ /** The `GET /v1/auth/check` payload (Task 6): per-provider statuses, never credentials. */
645
+ export interface AuthCheckResponse {
646
+ readonly providers: readonly AuthCheckProvider[];
647
+ }
648
+ /**
649
+ * One `GET /v1/extensions` entry: a loaded outcome enriched with the
650
+ * declared seam kinds, the idle-worker projection, the package
651
+ * manifest `version` (absent when the entry carries no readable
652
+ * manifest), and the declared HTTP routes (W5 Task 2 — present on
653
+ * every served entry, empty when the extension declared none). The
654
+ * webui's Extensions settings section renders this list.
655
+ */
656
+ export interface ServiceStatusWire {
657
+ readonly slug: string;
658
+ readonly name: string;
659
+ readonly user: string;
660
+ readonly state: "running" | "idle" | "failed";
661
+ readonly lastError?: string;
662
+ }
663
+ export interface ExtensionInfo {
664
+ readonly name: string;
665
+ readonly status: string;
666
+ readonly error?: string;
667
+ readonly version?: string;
668
+ /** The interaction kinds the factory declared (empty when none). */
669
+ readonly interactionKinds: readonly string[];
670
+ /** The ambient surface keys the factory declared (empty when none). */
671
+ readonly ambientKeys: readonly string[];
672
+ /** The extension's idle workers (empty when it declared none). */
673
+ readonly workers: readonly WorkerStatus[];
674
+ /** The declared HTTP routes, one `{method, path, auth}` each (W5 Task 2). */
675
+ routes?: readonly ExtensionRouteInfo[];
676
+ /** Per-user service states; admin-only inventory. */
677
+ readonly services: readonly ServiceStatusWire[];
678
+ /** The declared surfaces, one `{id, title, icon, placement}` each (U3 Task 1); absent when none. */
679
+ surfaces?: readonly ExtensionSurfaceInfo[];
680
+ /**
681
+ * The channel ids this extension contributes (gateway C1, spec §7):
682
+ * present only on rows of extensions whose factory result carried
683
+ * `channels`; unique across every loaded extension (a collision
684
+ * quarantines the later loader). Absent when the extension declared
685
+ * none.
686
+ */
687
+ readonly channels?: readonly string[];
688
+ /**
689
+ * The inert marker (gateway C2): `"no gateway"` on rows of
690
+ * extensions that contributed `channels` while NO gateway engine
691
+ * loaded — their adapters are inert (nothing drives them; one boot
692
+ * warning says so). Absent everywhere else.
693
+ */
694
+ readonly inert?: "no gateway";
695
+ /**
696
+ * Built-in extensions (Task 2, pi parity): `true` marks a core-shipped
697
+ * built-in (codemode / MCP / tool search / OAuth); ABSENT on configured
698
+ * extensions. A replaced built-in reports its row with `builtin: true`
699
+ * and `replacedBy` (its status is the skip, never `"ok"`); a disabled
700
+ * built-in is absent from the list entirely.
701
+ */
702
+ readonly builtin?: boolean;
703
+ /** The configured specifier that replaced this built-in (replacement rule). */
704
+ readonly replacedBy?: string;
705
+ }
706
+ /** One `GET /v1/extensions` entry's route rows (W5 Task 2, spec §9.2). */
707
+ export interface ExtensionRouteInfo {
708
+ readonly method: string;
709
+ /** The declared relative path — mounted at `/v1/ext/<slug>/<path>`. */
710
+ readonly path: string;
711
+ readonly auth: string;
712
+ }
713
+ /**
714
+ * One `GET /v1/extensions` surface row (UI-blocks U3 Task 1, spec
715
+ * §7.1): the extension-declared views and panels — what the channels'
716
+ * rail (views) and right panel (panels) register from. One row per
717
+ * declared surface, in declaration order (views before panels).
718
+ */
719
+ export interface ExtensionSurfaceInfo {
720
+ readonly id: string;
721
+ readonly title: string;
722
+ readonly icon: string;
723
+ /** The declared placement; `settings` sections (§13.3) are not in this workstream. */
724
+ readonly placement: "view" | "panel";
725
+ /** The declared refresh cadence (spec §7.1); channels refetch data on
726
+ * this interval while the surface is visible. Absent → no cadence. */
727
+ readonly refreshMs?: number;
728
+ /** §7.3 shared-data flag: a sharedData surface's `since` fetches may
729
+ * answer with `ops` and NO `data` — channels fall back to a full
730
+ * fetch (ops folding is U1). Absent → per-instance data. */
731
+ readonly sharedData?: boolean;
732
+ }
733
+ /** The `GET /v1/extensions` payload (spec §7.7). */
734
+ export interface ExtensionsResponse {
735
+ readonly extensions: readonly ExtensionInfo[];
736
+ }
737
+ /**
738
+ * The `GET /v1/settings/sections` payload (UI-blocks U4 Task 4, spec
739
+ * §13.4): the settings panel's nav list + the search index. Core's
740
+ * sections come first (slug `core`), then the loaded extensions'
741
+ * sections ordered by slug. The index covers every searchable row —
742
+ * a section title, a core settings key, an extension section's field —
743
+ * with the section id a hit jumps to.
744
+ */
745
+ export interface SettingsSectionsResponse {
746
+ readonly sections: readonly SettingsSectionEntry[];
747
+ readonly index: readonly SettingsSectionIndexEntry[];
748
+ }
749
+ /** One nav entry: the section's mount (slug + id) and its display. */
750
+ export interface SettingsSectionEntry {
751
+ readonly slug: string;
752
+ readonly id: string;
753
+ readonly title: string;
754
+ readonly icon: string;
755
+ /** Personal sections group under My settings and store caller-owned data. */
756
+ readonly scope?: "user";
757
+ /** Explicit admin gate (user sections are otherwise non-admin by default). */
758
+ readonly admin?: boolean;
759
+ }
760
+ /** One search-index row (the label search's hit shape, spec §13.4). */
761
+ export interface SettingsSectionIndexEntry {
762
+ /** The section the hit belongs to (the nav target). */
763
+ readonly slug: string;
764
+ readonly sectionId: string;
765
+ /** The row kind: a core settings key, an extension form field, or a section title. */
766
+ readonly kind: "core-key" | "field" | "section";
767
+ /** The searchable text (the field/section label). */
768
+ readonly label: string;
769
+ /** The field's help string, when it declares one. */
770
+ readonly help?: string;
771
+ /** The core key path / the field's form name, when the row has one. */
772
+ readonly key?: string;
773
+ }
774
+ /**
775
+ * The settings search's filter (UI-blocks U4 Task 4 round-2): the ONE
776
+ * implementation both channels run — the webui panel and the TUI view
777
+ * previously carried byte-identical copies, which drifted apart once
778
+ * (the TUI's lacked the key match). Case-insensitive over label + help
779
+ * + key; an empty query filters to nothing (the picker carries the
780
+ * full list).
781
+ */
782
+ export declare function filterSettingsIndex(index: readonly SettingsSectionIndexEntry[], query: string): readonly SettingsSectionIndexEntry[];
783
+ /**
784
+ * A `GET /v1/extensions/:slug/secrets` entry: the key and its winning
785
+ * source — NEVER the value (the API lists and writes secrets; it never
786
+ * returns them).
787
+ */
788
+ export interface ExtensionSecret {
789
+ readonly key: string;
790
+ readonly source: "stored" | "env";
791
+ }
792
+ /** The `GET`/`PUT`/`DELETE `/v1/extensions/:slug/secrets` payloads: the key list. */
793
+ export interface ExtensionSecretsResponse {
794
+ readonly secrets: readonly ExtensionSecret[];
795
+ }
796
+ /**
797
+ * The `GET`/`PUT /v1/extensions/:slug/settings` payload (UI-blocks U4,
798
+ * spec §13.2): the stored object verbatim under `settings`, plus the
799
+ * pending-restart slot. Extension-declared fields default to LIVE
800
+ * (§13.1), so `pendingRestart` is `[]` today; the field exists so the
801
+ * §13.4 callout renders from one shape across core and extension
802
+ * settings.
803
+ */
804
+ export interface ExtensionSettingsResponse {
805
+ readonly settings: Record<string, unknown>;
806
+ readonly pendingRestart: readonly string[];
807
+ }
808
+ export type { CommandsResponse, CommandRunResponse, SlashCommand } from "./commands.js";
809
+ export type { SessionMeta, SessionMetaPatch } from "./session-meta.js";
810
+ /**
811
+ * The `GET /v1/auth/whoami` payload (core hardening W1 Task 3, spec
812
+ * §5.8): the resolved principal, verbatim. `user` is absent only under
813
+ * the naive empty-method path with no `X-Sigrun-User` header;
814
+ * `service` marks the env service token. `method` mirrors the extension
815
+ * contract's `Principal.method` (gateway C4b added `"host"` — an
816
+ * in-process host-built principal; no HTTP route ever resolves one, but
817
+ * the two unions stay aligned so the whoami build from a principal can
818
+ * never drift from the wire type).
819
+ */
820
+ export interface WhoamiResponse {
821
+ readonly user?: string;
822
+ readonly name?: string;
823
+ readonly email?: string;
824
+ /** The caller's own workspace/grants; never another directory user's. */
825
+ readonly workspace?: string;
826
+ readonly grants?: readonly {
827
+ readonly path: string;
828
+ readonly access: "ro" | "rw";
829
+ }[];
830
+ readonly admin: boolean;
831
+ readonly service: boolean;
832
+ readonly method: "none" | "token" | "session" | "proxy" | "host";
833
+ readonly provider?: string;
834
+ }
835
+ /**
836
+ * The `GET /v1/onboarding` payload (core hardening W1 Task 4, spec
837
+ * §5.8): what an unauthenticated client needs to drive the claim flow.
838
+ * `codeRequired` is false on a loopback bind or when the request itself
839
+ * carries a trusted proxy identity (either is trust enough — no code);
840
+ * `proxyIdentity` is present exactly then, carrying what the headers
841
+ * vouch for so the webui can render its confirm screen.
842
+ */
843
+ export interface OnboardingState {
844
+ readonly state: "unclaimed" | "claimed";
845
+ readonly methods: readonly ("token" | "proxy")[];
846
+ readonly codeRequired: boolean;
847
+ readonly proxyIdentity?: {
848
+ /** The would-be directory key; `subject` is the raw proxy header. */
849
+ readonly user: string;
850
+ readonly subject: string;
851
+ readonly name?: string;
852
+ readonly email?: string;
853
+ };
854
+ }
855
+ /**
856
+ * One directory user on the wire (Task 4): the full record — emails are
857
+ * admin-visible material and the routes that serve this type are
858
+ * admin-gated. `createdAt` is epoch ms.
859
+ *
860
+ * `admin` is the STORED flag (the users-admin UI toggles it); `groups`
861
+ * is the deduplicated union of stored source memberships. `groupAdmin`
862
+ * is DERIVED from those groups and `server.auth.adminGroups`, retained
863
+ * for one release for the users UI; the last-admin guard counts `admin`
864
+ * alone.
865
+ */
866
+ export interface UserWire {
867
+ readonly user: string;
868
+ readonly name?: string;
869
+ readonly email?: string;
870
+ readonly admin: boolean;
871
+ /** DERIVED from stored groups and configured adminGroups; never persisted. */
872
+ readonly groupAdmin?: boolean;
873
+ /** The deduplicated union of groups stored by authenticating sources. */
874
+ readonly groups?: readonly string[];
875
+ readonly workspace?: string;
876
+ readonly grants?: readonly {
877
+ readonly path: string;
878
+ readonly access: "ro" | "rw";
879
+ }[];
880
+ readonly osUser?: string;
881
+ readonly createdAt: number;
882
+ readonly source: "proxy" | "invite" | "cli" | "login";
883
+ }
884
+ /** The `POST /v1/onboarding/claim` payload (Task 4): the user (plus the
885
+ * minted token when `issueToken` was requested — shown once, never
886
+ * persisted). */
887
+ export interface ClaimResponse {
888
+ readonly user: UserWire;
889
+ readonly token?: string;
890
+ }
891
+ /** The `POST /v1/users/invites` payload (Task 4): the one-time code is
892
+ * shown once, never stored in the clear. */
893
+ export interface InviteResponse {
894
+ readonly id: string;
895
+ readonly code: string;
896
+ readonly expiresAt: number;
897
+ }
898
+ /**
899
+ * One API token on the wire (Task 4): the token store's record minus the
900
+ * `sha256` digest — digests never leave the server. Created by the
901
+ * claim route (`issueToken`) and `POST /v1/auth/tokens`.
902
+ */
903
+ export interface TokenWire {
904
+ readonly id: string;
905
+ readonly name: string;
906
+ readonly user: string;
907
+ readonly createdAt: number;
908
+ readonly kind?: "api" | "session";
909
+ readonly expiresAt?: number;
910
+ }
911
+ /** The `POST /v1/auth/tokens` payload (Task 4): the secret once, the
912
+ * digest-free record alongside it. */
913
+ export interface TokenCreateResponse {
914
+ readonly token: string;
915
+ readonly record: TokenWire;
916
+ }
917
+ /**
918
+ * One external identity linked to a directory user (multi-user §11.3):
919
+ * the `accounts.json` record verbatim. `provider` is `proxy`,
920
+ * `chat:<channel>`, or `login:<slug>:<id>`; `subject` is the provider's
921
+ * stable subject (numeric id, `+E.164`, email-shaped — opaque to sigrun).
922
+ *
923
+ * Served to extensions as `host.accounts` and to users as
924
+ * `GET /v1/auth/accounts`.
925
+ */
926
+ export interface AccountLinkWire {
927
+ readonly provider: string;
928
+ readonly subject: string;
929
+ readonly user: string;
930
+ readonly linkedAt: number;
931
+ readonly via: "login" | "self" | "pairing" | "admin" | "proxy";
932
+ }
933
+ /**
934
+ * The `GET /v1/auth/accounts` payload (multi-user §11.3): the caller's
935
+ * links, or one named user's for an admin.
936
+ */
937
+ export interface AccountsResponse {
938
+ readonly accounts: readonly AccountLinkWire[];
939
+ }
940
+ /**
941
+ * One host event on the `GET /v1/events` SSE wire: `event: <type>` plus
942
+ * this object as the single-line `data:` JSON. Conversation-scoped
943
+ * events are delivered only when the connecting principal's `accessOf`
944
+ * against a FRESH SessionMeta snapshot is not `"none"` (per-event
945
+ * re-check: share → delivered, un-share → withheld);
946
+ * `providers.changed` reaches every authenticated principal;
947
+ * `job.changed` carries the changed job's `JobWire` summary WITHOUT
948
+ * `access` — the events stream computes and adds it per principal before
949
+ * delivery (the hub's copy is access-neutral).
950
+ * No replay: a client that reconnects refetches (the list GET); the
951
+ * stream only nudges.
952
+ */
953
+ export type HostEventWire = {
954
+ readonly type: "conversation.created";
955
+ readonly conversationId: number;
956
+ } | {
957
+ readonly type: "conversation.changed";
958
+ readonly conversationId: number;
959
+ /** The summary fields that changed, with their new values. */
960
+ readonly fields: Partial<ConversationSummary>;
961
+ } | {
962
+ readonly type: "run.admitted";
963
+ readonly conversationId: number;
964
+ readonly submissionId: number;
965
+ } | {
966
+ readonly type: "run.settled";
967
+ readonly conversationId: number;
968
+ readonly outcome: "answered" | "unanswered";
969
+ readonly reason?: string;
970
+ } | {
971
+ readonly type: "interaction.pending" | "interaction.settled";
972
+ readonly conversationId: number;
973
+ readonly callId: string;
974
+ readonly kind: string;
975
+ } | {
976
+ readonly type: "ambient.changed";
977
+ readonly conversationId: number;
978
+ readonly key: string;
979
+ } | {
980
+ readonly type: "providers.changed";
981
+ readonly provider: string;
982
+ readonly status: string;
983
+ } | {
984
+ /** A task table write with a TERMINAL status (jobs Task 3, §8.1): the jobs observer settles `task`-action run records from it — `completed` → `succeeded`, any other terminal → `failed` + `error`. Hub-side only (not an SSE wire event): the task table is host machinery, not principal-facing surface. */
985
+ readonly type: "taskSettled";
986
+ readonly taskId: number;
987
+ readonly status: string;
988
+ readonly error?: string;
989
+ } | {
990
+ /** W4 Task 4 (§8.4): the changed job's summary, WITHOUT `access` — the events stream computes `accessOf` per principal and adds it before delivery (a hidden job's event is withheld entirely). */
991
+ readonly type: "job.changed";
992
+ readonly job: Omit<JobWire, "access">;
993
+ } | {
994
+ /** UI-blocks U4 (spec §13.2): a settings write landed. `scope` is
995
+ * `"server"` for `PATCH /v1/settings` or `{extension: slug}` for
996
+ * an extension's settings PUT; `keys` are the flattened paths the
997
+ * write touched (dotted, the §13.1 registry vocabulary — extension
998
+ * writes name their own top-level object keys). Delivered to
999
+ * ADMINS only (settings are operator surface; the events route's
1000
+ * exhaustive switch enforces it).
1001
+ */
1002
+ readonly type: "settings.changed";
1003
+ readonly scope: "server" | {
1004
+ readonly extension: string;
1005
+ };
1006
+ readonly keys: readonly string[];
1007
+ } | {
1008
+ /** UI-blocks U3 Task 4 (spec §7.3): one surface instance changed. `k` is the hashed instance key (sha256(slug + "\0" + id + "\0" + key) hex) — the event NEVER carries params or data (render/data are principal-specific; the watcher refetches as itself). Delivered only to streams in the watcher registry whose stored access is not `"none"`. */
1009
+ readonly type: "surface.changed";
1010
+ readonly slug: string;
1011
+ readonly id: string;
1012
+ readonly k: string;
1013
+ readonly rev: number;
1014
+ readonly treeRev: number;
1015
+ } | {
1016
+ /** UI-blocks U5 Task 2 (spec §8.2): one CHAT block surface changed. The chat address is `(conversationId, entryId)` — no secret to watchers of that conversation (unlike params) — so the event names them directly instead of the hashed `k`. Delivered through the conversation-scoped path: the events route re-snapshots the SessionMeta and checks `accessOf` per event (hidden conversations see nothing). */
1017
+ readonly type: "surface.changed";
1018
+ readonly conversationId: number;
1019
+ readonly entryId: string;
1020
+ readonly rev: number;
1021
+ };