yarramate 0.23.0 → 1.0.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 (159) hide show
  1. package/README.md +13 -4
  2. package/catalogues/core-enrichment.yaml +79 -55
  3. package/dist/adapter-mapping.js +6 -1
  4. package/dist/adapters/likec4-cli.js +36 -7
  5. package/dist/adapters/visual/client.d.ts +2 -2
  6. package/dist/adapters/visual/client.js +79 -10
  7. package/dist/adapters/visual/protocol-contract.d.ts +124 -10
  8. package/dist/adapters/visual/protocol-contract.js +12 -1
  9. package/dist/adapters/visual/protocol.d.ts +63 -0
  10. package/dist/adapters/visual/protocol.js +96 -5
  11. package/dist/adapters/visual/session-server.d.ts +1 -6
  12. package/dist/adapters/visual/session-server.js +243 -143
  13. package/dist/adapters/visual/session-store.d.ts +9 -0
  14. package/dist/adapters/visual/session-store.js +14 -5
  15. package/dist/adapters/visual/view-identity.d.ts +135 -0
  16. package/dist/adapters/visual/view-identity.js +200 -0
  17. package/dist/adapters/visual/wire.d.ts +19 -4
  18. package/dist/adapters/visual/workspace-model.d.ts +116 -0
  19. package/dist/adapters/visual/workspace-model.js +212 -0
  20. package/dist/adapters/visual-cli.d.ts +1 -1
  21. package/dist/adapters/visual-cli.js +30 -21
  22. package/dist/apply-cli.d.ts +2 -0
  23. package/dist/apply-cli.js +83 -0
  24. package/dist/apply-command.d.ts +84 -3
  25. package/dist/apply-command.js +218 -109
  26. package/dist/archimate-relationships.generated.d.ts +22 -0
  27. package/dist/archimate-relationships.generated.js +156 -0
  28. package/dist/ask-command.js +18 -6
  29. package/dist/attestation-staleness.js +2 -2
  30. package/dist/changed.js +1 -1
  31. package/dist/check-command.js +13 -5
  32. package/dist/cli-support.d.ts +1 -1
  33. package/dist/cli-support.js +1 -1
  34. package/dist/cli.js +1 -1
  35. package/dist/compiler.d.ts +31 -3
  36. package/dist/compiler.js +321 -81
  37. package/dist/concept-drafting.d.ts +27 -0
  38. package/dist/concept-drafting.js +75 -0
  39. package/dist/core-contract.js +6 -1
  40. package/dist/deletion-drafting.d.ts +52 -0
  41. package/dist/deletion-drafting.js +100 -0
  42. package/dist/evidence.js +6 -1
  43. package/dist/graph-projection.d.ts +14 -0
  44. package/dist/graph-projection.js +4 -0
  45. package/dist/index.d.ts +8 -1
  46. package/dist/index.js +8 -1
  47. package/dist/interrogate-command.js +63 -17
  48. package/dist/kind-label.d.ts +4 -3
  49. package/dist/kind-label.js +4 -3
  50. package/dist/nesting.d.ts +15 -0
  51. package/dist/nesting.js +2 -0
  52. package/dist/notation/archimate.d.ts +1 -1
  53. package/dist/notation/archimate.js +108 -9
  54. package/dist/profile.d.ts +9 -12
  55. package/dist/profile.js +17 -26
  56. package/dist/projection.d.ts +62 -3
  57. package/dist/projection.js +127 -43
  58. package/dist/relationship-drafting.d.ts +51 -0
  59. package/dist/relationship-drafting.js +92 -0
  60. package/dist/relationship-matrix.d.ts +14 -0
  61. package/dist/relationship-matrix.js +74 -0
  62. package/dist/shipped-profile.d.ts +1 -1
  63. package/dist/shipped-profile.js +19 -5
  64. package/dist/source-store.d.ts +95 -0
  65. package/dist/source-store.js +170 -0
  66. package/dist/subject-references.js +1 -1
  67. package/dist/visual-app/assets/index-BLcpI8WN.js +394 -0
  68. package/dist/visual-app/assets/index-DLutIWES.css +1 -0
  69. package/dist/visual-app/index.html +2 -2
  70. package/dist/visual-app-lib/editor.d.ts +1 -0
  71. package/dist/visual-app-lib/editor.js +90968 -0
  72. package/dist/visual-app-lib/styles.css +2 -0
  73. package/dist/visual-app-lib/types/adapter-mapping.d.ts +40 -0
  74. package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +455 -0
  75. package/dist/visual-app-lib/types/adapters/visual/view-identity.d.ts +135 -0
  76. package/dist/visual-app-lib/types/adapters/visual/wire.d.ts +141 -0
  77. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +116 -0
  78. package/dist/visual-app-lib/types/apply-command.d.ts +93 -0
  79. package/dist/visual-app-lib/types/archimate-relationships.generated.d.ts +22 -0
  80. package/dist/visual-app-lib/types/brief.d.ts +5 -0
  81. package/dist/visual-app-lib/types/compiler.d.ts +155 -0
  82. package/dist/visual-app-lib/types/concept-drafting.d.ts +27 -0
  83. package/dist/visual-app-lib/types/deletion-drafting.d.ts +52 -0
  84. package/dist/visual-app-lib/types/evidence.d.ts +62 -0
  85. package/dist/visual-app-lib/types/graph-claims.d.ts +18 -0
  86. package/dist/visual-app-lib/types/graph-projection.d.ts +77 -0
  87. package/dist/visual-app-lib/types/kind-label.d.ts +9 -0
  88. package/dist/visual-app-lib/types/nesting.d.ts +15 -0
  89. package/dist/visual-app-lib/types/notation/archimate.d.ts +71 -0
  90. package/dist/visual-app-lib/types/operations.d.ts +157 -0
  91. package/dist/visual-app-lib/types/profile.d.ts +47 -0
  92. package/dist/visual-app-lib/types/projection.d.ts +113 -0
  93. package/dist/visual-app-lib/types/relationship-drafting.d.ts +51 -0
  94. package/dist/visual-app-lib/types/relationship-matrix.d.ts +14 -0
  95. package/dist/visual-app-lib/types/shipped-profile.d.ts +2 -0
  96. package/dist/visual-app-lib/types/source-document.d.ts +36 -0
  97. package/dist/visual-app-lib/types/source-store.d.ts +95 -0
  98. package/dist/visual-app-lib/types/subject-references.d.ts +92 -0
  99. package/dist/visual-app-lib/types/visual-app/App.d.ts +15 -0
  100. package/dist/visual-app-lib/types/visual-app/badges.d.ts +7 -0
  101. package/dist/visual-app-lib/types/visual-app/changeset-tray.d.ts +106 -0
  102. package/dist/visual-app-lib/types/visual-app/confirm-dialog.d.ts +14 -0
  103. package/dist/visual-app-lib/types/visual-app/connection-panel.d.ts +23 -0
  104. package/dist/visual-app-lib/types/visual-app/context-menu-model.d.ts +187 -0
  105. package/dist/visual-app-lib/types/visual-app/context-menu.d.ts +24 -0
  106. package/dist/visual-app-lib/types/visual-app/describe-query.d.ts +9 -0
  107. package/dist/visual-app-lib/types/visual-app/editor-host.d.ts +70 -0
  108. package/dist/visual-app-lib/types/visual-app/faults.d.ts +26 -0
  109. package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +73 -0
  110. package/dist/visual-app-lib/types/visual-app/kind-icons.d.ts +3 -0
  111. package/dist/visual-app-lib/types/visual-app/local-host.d.ts +38 -0
  112. package/dist/visual-app-lib/types/visual-app/mount.d.ts +56 -0
  113. package/dist/visual-app-lib/types/visual-app/prompt-dialog.d.ts +24 -0
  114. package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +69 -0
  115. package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +142 -0
  116. package/dist/visual-app-lib/types/visual-app/quick-filter.d.ts +11 -0
  117. package/dist/visual-app-lib/types/visual-app/relationship-kind-options.d.ts +50 -0
  118. package/dist/visual-app-lib/types/visual-app/save-view.d.ts +86 -0
  119. package/dist/visual-app-lib/types/visual-app/section-stack.d.ts +59 -0
  120. package/dist/visual-app-lib/types/visual-app/session-client.d.ts +38 -0
  121. package/dist/visual-app-lib/types/visual-app/state.d.ts +328 -0
  122. package/dist/visual-app-lib/types/visual-app/subject-draft-panel.d.ts +22 -0
  123. package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +42 -0
  124. package/dist/visual-app-lib/types/visual-app/view-tree-model.d.ts +122 -0
  125. package/dist/visual-app-lib/types/visual-app/view-tree.d.ts +32 -0
  126. package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +268 -0
  127. package/dist/visual-app-lib/types/workspace.d.ts +29 -0
  128. package/dist/workspace.js +7 -1
  129. package/docs/CONSUMING-YARRAMATE.md +51 -2
  130. package/package.json +9 -2
  131. package/schema/yarramate-adapter-mapping.schema.json +1 -1
  132. package/schema/yarramate-ask-result.schema.json +24 -3
  133. package/schema/yarramate-check-result.schema.json +9 -0
  134. package/schema/yarramate-diagnostic-result.schema.json +9 -0
  135. package/schema/yarramate-document.schema.json +6 -0
  136. package/schema/yarramate-evidence-report.schema.json +1 -1
  137. package/schema/yarramate-evidence.schema.json +1 -1
  138. package/schema/yarramate-graph-v2.schema.json +34 -9
  139. package/schema/yarramate-likec4-generated-project-v2.schema.json +1 -1
  140. package/schema/yarramate-likec4-generated-project.schema.json +1 -1
  141. package/schema/yarramate-likec4-project.schema.json +1 -1
  142. package/schema/yarramate-projection-result.schema.json +3 -7
  143. package/schema/yarramate-projection.schema.json +16 -16
  144. package/schema/yarramate-reconciliation-report.schema.json +2 -2
  145. package/schema/yarramate-rtm.schema.json +1 -1
  146. package/schema/yarramate-state-comparison.schema.json +1 -1
  147. package/schema/yarramate-visual-diagnostic-result.schema.json +9 -0
  148. package/schema/yarramate-visual-event.schema.json +40 -41
  149. package/schema/yarramate-visual-graph.schema.json +8 -0
  150. package/schema/yarramate-visual-handoff.schema.json +3 -3
  151. package/schema/yarramate-visual-session-descriptor.schema.json +6 -6
  152. package/schema/yarramate-visual-session-started.schema.json +10 -9
  153. package/schema/yarramate-visual-status.schema.json +3 -3
  154. package/skills/yarramate-architecture/SKILL.md +3 -3
  155. package/skills/yarramate-architecture/references/modelling-patterns.md +23 -44
  156. package/skills/yarramate-architecture/references/native-authoring.md +32 -49
  157. package/skills/yarramate-architecture/references/visual-conversations.md +13 -10
  158. package/dist/visual-app/assets/index-CRd1Khma.js +0 -354
  159. package/dist/visual-app/assets/index-tZSHlU-a.css +0 -1
@@ -10,8 +10,19 @@
10
10
  */
11
11
  import type { CanvasGraph } from "../../graph-projection.js";
12
12
  import type { YarramateApplyResult, YarramateOperation } from "../../operations.js";
13
- import type { ProjectionDefinition, ProjectionQuery } from "../../projection.js";
14
- export declare const VISUAL_PROTOCOL_VERSION: "yarramate/visual-protocol/v3";
13
+ import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
14
+ /**
15
+ * The wire this adapter speaks. v5 makes a view a staged change rather than an
16
+ * immediate write (ADR 0103): a commit carries `viewOperations` beside its
17
+ * model operations, a model frame carries the projection digests those
18
+ * operations pin against, and the `view.save` event that wrote a projection
19
+ * the moment it was composed is gone. A v4 peer and a v5 peer refuse each
20
+ * other at the first document exchanged.
21
+ *
22
+ * v4 retyped every path-carrying field from a bare native string to a
23
+ * canonical local `file:` URI (ADR 0096).
24
+ */
25
+ export declare const VISUAL_PROTOCOL_VERSION: "yarramate/visual-protocol/v5";
15
26
  export declare const VISUAL_LIMITS: {
16
27
  readonly messageBytes: number;
17
28
  readonly modelBytes: number;
@@ -29,6 +40,18 @@ export interface VisualDiagnostic {
29
40
  readonly pointer: string;
30
41
  readonly line: number;
31
42
  readonly column: number;
43
+ /**
44
+ * The subjects this diagnostic is about, most relevant first, when its
45
+ * pointer names one (ADR 0102, extending the derivation Core added for
46
+ * exactly this consumer).
47
+ *
48
+ * Absence is meaningful and is not "not yet populated": a diagnostic with no
49
+ * subject belongs to no subject, such as a YAML parse failure, a
50
+ * whole-document schema violation, a projection's own definition, or a
51
+ * manifest. That is what lets a canvas route one to an element and the other
52
+ * to a lane of its own, rather than showing a failure with nothing marked.
53
+ */
54
+ readonly subjects?: readonly string[];
32
55
  }
33
56
  export interface VisualDiagnosticResult {
34
57
  readonly format: "yarramate/visual-diagnostic-result/v1";
@@ -56,7 +79,7 @@ export interface VisualSessionRequest {
56
79
  readonly initialModel: VisualModel;
57
80
  }
58
81
  export interface VisualSessionStarted {
59
- readonly format: "yarramate/visual-session-started/v1";
82
+ readonly format: "yarramate/visual-session-started/v2";
60
83
  readonly protocolVersion: typeof VISUAL_PROTOCOL_VERSION;
61
84
  readonly sessionId: string;
62
85
  readonly authority: VisualAuthority;
@@ -65,18 +88,25 @@ export interface VisualSessionStarted {
65
88
  readonly browserUrl: string;
66
89
  readonly webSocketUrl: string;
67
90
  readonly origin: string;
91
+ /** Canonical local `file:` URI. Copy it back verbatim as the argument to
92
+ * `wait`/`respond`/`status`/`recover`/`stop`; it is never hand-typed as a
93
+ * native path. Minted by `toWireFileUri`, read by `fromWireFileUri`. */
68
94
  readonly descriptorPath: string;
95
+ /** Canonical local `file:` URI, as `descriptorPath`. */
69
96
  readonly sessionRoot: string;
70
97
  readonly capabilities: VisualCapabilities;
71
98
  readonly startedAt: string;
72
99
  }
73
100
  export interface VisualSessionDescriptor {
74
- readonly format: "yarramate/visual-session-descriptor/v1";
101
+ readonly format: "yarramate/visual-session-descriptor/v2";
75
102
  readonly protocolVersion: typeof VISUAL_PROTOCOL_VERSION;
76
103
  readonly sessionId: string;
77
104
  readonly origin: string;
78
105
  readonly agentCapability: string;
106
+ /** Canonical local `file:` URI. Proven against the session actually opened
107
+ * before `agentCapability` is ever spent. */
79
108
  readonly sessionRoot: string;
109
+ /** Canonical local `file:` URI, as `sessionRoot`. */
80
110
  readonly journalPath: string;
81
111
  readonly createdAt: string;
82
112
  }
@@ -97,10 +127,39 @@ export interface VisualViewSummary {
97
127
  readonly description: string;
98
128
  readonly query: ProjectionQuery;
99
129
  readonly presentation: ProjectionDefinition["presentation"];
130
+ /**
131
+ * Manifest-relative path of the projection document this view was read from,
132
+ * as `VisualRenderedModel.documents` names a document — not the `file:` URI
133
+ * ADR 0096 mints for the descriptor and session root, which name things
134
+ * outside the workspace.
135
+ *
136
+ * The tree derives its folders from these paths, so a workspace that keeps
137
+ * `current/` and `target/` projections in directories gets a foldered view
138
+ * list without the projection format gaining a folder concept of its own.
139
+ */
140
+ readonly path: string;
141
+ /**
142
+ * How many subjects this view's query matches, resolved by the runtime the
143
+ * way `filter.query` is: a `ProjectionQuery` needs the semantic graph, which
144
+ * the browser does not have. Recomputed and re-sent whenever the workspace
145
+ * recompiles, since landing a changeset moves these counts.
146
+ */
147
+ readonly subjectCount: number;
100
148
  }
101
149
  export interface VisualKindOption {
102
150
  readonly id: string;
103
151
  readonly label: string;
152
+ /**
153
+ * The nearest core-profile kind this one descends from, as a label — the
154
+ * same resolution `CanvasNode.coreKindLabel` and `CanvasEdge.coreKindLabel`
155
+ * carry, for a kind nothing has been authored with yet.
156
+ *
157
+ * Without it a browser can read a palette but cannot judge it: the ArchiMate
158
+ * table is keyed on core kinds, so an editor offering an extension kind has
159
+ * no way to ask whether the pairing permits what that kind descends from,
160
+ * and can put a `YM404` one click away. Equal to `label` for a core kind.
161
+ */
162
+ readonly coreLabel: string;
104
163
  }
105
164
  export interface VisualFilterQueryPayload {
106
165
  readonly query: ProjectionQuery;
@@ -108,6 +167,23 @@ export interface VisualFilterQueryPayload {
108
167
  export interface VisualFilterResultPayload {
109
168
  readonly query: ProjectionQuery;
110
169
  readonly matchedIds: readonly string[];
170
+ /**
171
+ * Every concept this query DROPPED, each with the facet that dropped it, as
172
+ * `explainProjection` reports it. The editor needs it to say why a subject
173
+ * is not on the canvas: a query that quietly drops the one subject the
174
+ * reviewer was looking for is otherwise indistinguishable from a model that
175
+ * does not hold it.
176
+ *
177
+ * A frame field, not a document field. `yarramate/projection-result/v1` is
178
+ * `additionalProperties: false` and this is a question about a query rather
179
+ * than part of what a projection IS, so the exclusions ride on the answer to
180
+ * the question that asked for them and nowhere else.
181
+ *
182
+ * Relationships are absent by construction: they enter a view through their
183
+ * endpoints rather than by matching a facet, so "why" for a relationship is
184
+ * a statement about the concepts it joins.
185
+ */
186
+ readonly excluded: readonly ProjectionExclusion[];
111
187
  }
112
188
  export interface VisualViewSavePayload {
113
189
  readonly id?: string;
@@ -122,8 +198,37 @@ export interface VisualLayoutPositions {
122
198
  readonly y: number;
123
199
  };
124
200
  }
201
+ /**
202
+ * A change to a projection document, staged rather than written (ADR 0103).
203
+ *
204
+ * These ride beside the model's operations rather than inside them: Core holds
205
+ * that a projection is never an operation's own target, and a view is
206
+ * presentation rather than semantics. Atomicity does not come from sharing a
207
+ * list - it comes from the runtime planning both and issuing one
208
+ * `SourceStore.writeAll`, so a view and the subjects it shows land together or
209
+ * not at all.
210
+ *
211
+ * `path` is manifest-relative, the way `VisualViewSummary.path` names it. A
212
+ * `write-view` at a path the manifest's patterns do not cover is refused
213
+ * rather than written, because a projection nothing loads is worse than a
214
+ * refusal (ADR 0043).
215
+ */
216
+ export type VisualViewOperation = {
217
+ readonly op: "write-view";
218
+ readonly path: string;
219
+ readonly projection: ProjectionDefinition;
220
+ } | {
221
+ readonly op: "delete-view";
222
+ readonly path: string;
223
+ };
125
224
  export interface VisualChangesetCommitPayload {
126
225
  readonly operations: readonly YarramateOperation[];
226
+ /**
227
+ * Projection writes and removals landing in the same batch as `operations`.
228
+ * Required, not optional, for the same reason `sourceDigests` is: a browser
229
+ * that may omit it is one whose commits mean two different things.
230
+ */
231
+ readonly viewOperations: readonly VisualViewOperation[];
127
232
  /**
128
233
  * What the browser believed each targeted document held when the rows were
129
234
  * staged — sha256 keyed by manifest-relative path, pinned at staging time and
@@ -134,6 +239,11 @@ export interface VisualChangesetCommitPayload {
134
239
  * Required, not optional: a browser that omits it is exactly the browser that
135
240
  * cannot detect the conflict, which is why this field is what makes the
136
241
  * protocol `v3`.
242
+ *
243
+ * One map covers both lists. On the model frame the two are kept apart,
244
+ * because there they mean different things — what the graph was compiled
245
+ * from, and what the views are — but a pin means the same thing for either:
246
+ * what the browser expected to find on disk.
137
247
  */
138
248
  readonly sourceDigests: Readonly<Record<string, string>>;
139
249
  }
@@ -187,10 +297,6 @@ export type VisualBrowserInput = {
187
297
  readonly type: "filter.query";
188
298
  readonly lastAcknowledgedSequence: number;
189
299
  readonly payload: VisualFilterQueryPayload;
190
- } | {
191
- readonly type: "view.save";
192
- readonly lastAcknowledgedSequence: number;
193
- readonly payload: VisualViewSavePayload;
194
300
  } | {
195
301
  readonly type: "changeset.commit";
196
302
  readonly lastAcknowledgedSequence: number;
@@ -213,7 +319,7 @@ interface VisualEventEnvelope<Type extends string, Payload> {
213
319
  readonly timestamp: string;
214
320
  readonly payload: Payload;
215
321
  }
216
- export type VisualEvent = VisualEventEnvelope<"chat.message", VisualChatMessagePayload> | VisualEventEnvelope<"choice.selected", VisualChoiceSelectedPayload> | VisualEventEnvelope<"view.navigate", VisualViewNavigatePayload> | VisualEventEnvelope<"session.end", VisualSessionEndPayload> | VisualEventEnvelope<"browser.connected", VisualBrowserConnectedPayload> | VisualEventEnvelope<"browser.disconnected", VisualBrowserDisconnectedPayload> | VisualEventEnvelope<"filter.query", VisualFilterQueryPayload> | VisualEventEnvelope<"view.save", VisualViewSavePayload> | VisualEventEnvelope<"changeset.commit", VisualChangesetCommitPayload> | VisualEventEnvelope<"layout.save", VisualLayoutSavePayload>;
322
+ export type VisualEvent = VisualEventEnvelope<"chat.message", VisualChatMessagePayload> | VisualEventEnvelope<"choice.selected", VisualChoiceSelectedPayload> | VisualEventEnvelope<"view.navigate", VisualViewNavigatePayload> | VisualEventEnvelope<"session.end", VisualSessionEndPayload> | VisualEventEnvelope<"browser.connected", VisualBrowserConnectedPayload> | VisualEventEnvelope<"browser.disconnected", VisualBrowserDisconnectedPayload> | VisualEventEnvelope<"filter.query", VisualFilterQueryPayload> | VisualEventEnvelope<"changeset.commit", VisualChangesetCommitPayload> | VisualEventEnvelope<"layout.save", VisualLayoutSavePayload>;
217
323
  /**
218
324
  * The filter a chat turn resolved to. The agent states `query` and nothing
219
325
  * else: the runtime evaluates it against the same graph a `filter.query`
@@ -258,6 +364,13 @@ export type VisualViewSaveResultPayload = {
258
364
  readonly ok: true;
259
365
  readonly id: string;
260
366
  readonly path: string;
367
+ /**
368
+ * What the saved query matches, stated by the side that can count it.
369
+ * The browser builds the new view's summary from what it sent plus this
370
+ * result, and a `ProjectionQuery` needs the semantic graph to resolve —
371
+ * without this the row would join the tree claiming zero subjects.
372
+ */
373
+ readonly subjectCount: number;
261
374
  } | {
262
375
  readonly ok: false;
263
376
  readonly diagnostics: readonly VisualDiagnostic[];
@@ -289,12 +402,13 @@ export type VisualResponse = VisualResponseEnvelope<"chat.response", VisualChatR
289
402
  export type VisualHandoffDecision = "completed" | "cancelled" | "failed";
290
403
  export type VisualTerminationReason = "user-ended" | "child-failed" | "browser-timeout" | "main-cancelled" | "server-failed" | "compiler-failed";
291
404
  export interface VisualHandoff extends VisualHandoffSummary {
292
- readonly format: "yarramate/visual-handoff/v1";
405
+ readonly format: "yarramate/visual-handoff/v2";
293
406
  readonly sessionId: string;
294
407
  readonly authority: VisualAuthority;
295
408
  readonly decision: VisualHandoffDecision;
296
409
  readonly terminationReason: VisualTerminationReason;
297
410
  readonly lastSequence: number;
411
+ /** Canonical local `file:` URI. */
298
412
  readonly transcriptPath: string;
299
413
  readonly transcript?: readonly (VisualEvent | VisualResponse)[];
300
414
  readonly completedAt: string;
@@ -1,4 +1,15 @@
1
- export const VISUAL_PROTOCOL_VERSION = "yarramate/visual-protocol/v3";
1
+ /**
2
+ * The wire this adapter speaks. v5 makes a view a staged change rather than an
3
+ * immediate write (ADR 0103): a commit carries `viewOperations` beside its
4
+ * model operations, a model frame carries the projection digests those
5
+ * operations pin against, and the `view.save` event that wrote a projection
6
+ * the moment it was composed is gone. A v4 peer and a v5 peer refuse each
7
+ * other at the first document exchanged.
8
+ *
9
+ * v4 retyped every path-carrying field from a bare native string to a
10
+ * canonical local `file:` URI (ADR 0096).
11
+ */
12
+ export const VISUAL_PROTOCOL_VERSION = "yarramate/visual-protocol/v5";
2
13
  export const VISUAL_LIMITS = {
3
14
  messageBytes: 64 * 1024,
4
15
  modelBytes: 5 * 1024 * 1024,
@@ -9,8 +9,71 @@ export * from './protocol-contract.js';
9
9
  * mints them for the initial model, the session server re-mints them on every
10
10
  * recompile and checks a commit's pins against the files on disk, and all three
11
11
  * are the same hash by construction rather than by three matching literals.
12
+ *
13
+ * There is a fourth, and it is not by construction. A commit's pins become the
14
+ * `expected` revisions a `SourceStore` compares before it writes (ADR 0100),
15
+ * and a store's revision is opaque: what `createFileSystemStore` mints happens
16
+ * to be this same sha256, which is the only reason a pin can be handed to it
17
+ * directly. The visual runtime only ever addresses a local filesystem, so that
18
+ * coincidence is safe today and a protocol version spent on carrying opaque
19
+ * revisions instead would buy nothing. It is held by a test rather than by a
20
+ * comment: `visual-protocol.test.ts` asserts the two agree, so a change to
21
+ * either side fails rather than silently making every commit's precondition
22
+ * unsatisfiable. That failure is when the protocol bump earns its cost.
12
23
  */
13
24
  export declare const digestOf: (source: string) => string;
25
+ /**
26
+ * Encode a native absolute path for the wire.
27
+ *
28
+ * Every filesystem path a protocol document carries is a canonical local
29
+ * `file:` URI, minted by Node's own `pathToFileURL`. A URI is invertible
30
+ * where the forward-slash string transform it replaces was not: a POSIX
31
+ * directory whose own name contains a literal backslash percent-encodes to
32
+ * `%5C` and stays distinguishable from a Windows separator, and a UNC path
33
+ * grows a non-empty host rather than passing as an ordinary POSIX-rooted
34
+ * path.
35
+ *
36
+ * Encoding cannot fail. `pathToFileURL` has no error path for an
37
+ * already-absolute native string, and every caller holds a `resolve`/`join`
38
+ * result it produced itself. A relative path reaching here is a programming
39
+ * error rather than a protocol fault, and throws as one.
40
+ */
41
+ export declare const toWireFileUri: (native: string) => string;
42
+ /** Why one wire path was refused. Each shape is reported as `YMVS414`. */
43
+ export type WireFileUriRefusal = 'malformed' | 'nonlocal' | 'noncanonical';
44
+ export type WireFileUriResult = {
45
+ readonly ok: true;
46
+ readonly value: string;
47
+ } | {
48
+ readonly ok: false;
49
+ readonly reason: WireFileUriRefusal;
50
+ };
51
+ /**
52
+ * Decode a wire path back to native, the way untrusted input has to be read.
53
+ *
54
+ * A descriptor's bearer capabilities are never spent on a document whose path
55
+ * fields have not passed this first, so the three refusals are checked in a
56
+ * fixed order and none of them is ever quietly repaired:
57
+ *
58
+ * - `malformed` — not a parseable `file:` URI, or one `fileURLToPath` itself
59
+ * rejects (`ERR_INVALID_URL`, `ERR_INVALID_URL_SCHEME`,
60
+ * `ERR_INVALID_FILE_URL_PATH`). A bare native path lands here too: it has
61
+ * no scheme, or, for a Windows drive root, one that is not `file:`.
62
+ * - `nonlocal` — a non-empty host, which `pathToFileURL` never produces for a
63
+ * local path: `file://server/share/x` names a network share. The host is
64
+ * read off the parsed URL rather than inferred from a `fileURLToPath`
65
+ * failure, because that call refuses a foreign host on POSIX but resolves
66
+ * one to a UNC path on Windows, and reading it directly is the only way one
67
+ * URI earns the same refusal on both.
68
+ * - `noncanonical` — decodes, host is empty, but re-encoding the decoded path
69
+ * does not reproduce the input byte-for-byte. Two spellings of one target is
70
+ * the aliasing this representation exists to close, so the other spelling is
71
+ * refused rather than normalized into the canonical one. `file://localhost/x`
72
+ * lands here rather than in `nonlocal`: WHATWG `URL` normalizes a
73
+ * `localhost` file host away, leaving a URI that is simply not the spelling
74
+ * this codec mints for `/x`.
75
+ */
76
+ export declare const fromWireFileUri: (uri: string) => WireFileUriResult;
14
77
  export declare const parseVisualModel: (input: unknown) => ParseResult<VisualModel>;
15
78
  export declare const parseVisualSessionRequest: (input: unknown) => ParseResult<VisualSessionRequest>;
16
79
  export declare const parseVisualSessionStarted: (input: unknown) => ParseResult<VisualSessionStarted>;
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { posix } from 'node:path';
2
+ import { isAbsolute, posix } from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
4
  import Ajv2020Module from 'ajv/dist/2020.js';
4
5
  import { describeSchemaViolation, readableSchemaErrors, } from '../../source-document.js';
5
6
  import visualDiagnosticResultSchema from '../../../schema/yarramate-visual-diagnostic-result.schema.json' with { type: 'json'
@@ -67,6 +68,17 @@ ajv.addSchema([
67
68
  * mints them for the initial model, the session server re-mints them on every
68
69
  * recompile and checks a commit's pins against the files on disk, and all three
69
70
  * are the same hash by construction rather than by three matching literals.
71
+ *
72
+ * There is a fourth, and it is not by construction. A commit's pins become the
73
+ * `expected` revisions a `SourceStore` compares before it writes (ADR 0100),
74
+ * and a store's revision is opaque: what `createFileSystemStore` mints happens
75
+ * to be this same sha256, which is the only reason a pin can be handed to it
76
+ * directly. The visual runtime only ever addresses a local filesystem, so that
77
+ * coincidence is safe today and a protocol version spent on carrying opaque
78
+ * revisions instead would buy nothing. It is held by a test rather than by a
79
+ * comment: `visual-protocol.test.ts` asserts the two agree, so a change to
80
+ * either side fails rather than silently making every commit's precondition
81
+ * unsatisfiable. That failure is when the protocol bump earns its cost.
70
82
  */
71
83
  export const digestOf = (source) => createHash('sha256').update(source).digest('hex');
72
84
  // Diagnostics report the document they came from rather than a source file,
@@ -81,8 +93,8 @@ const visualValidator = (document, reference = `https://yarramate.org/schema/${d
81
93
  };
82
94
  const validateVisualModel = visualValidator('visual-model/v1');
83
95
  const validateVisualSessionRequest = visualValidator('visual-session-request/v1');
84
- const validateVisualSessionStarted = visualValidator('visual-session-started/v1');
85
- const validateVisualSessionDescriptor = visualValidator('visual-session-descriptor/v1');
96
+ const validateVisualSessionStarted = visualValidator('visual-session-started/v2');
97
+ const validateVisualSessionDescriptor = visualValidator('visual-session-descriptor/v2');
86
98
  const BROWSER_INPUT_REFERENCE = 'https://yarramate.org/schema/visual-event/v1#/$defs/browserInput';
87
99
  const validateVisualBrowserInput = visualValidator('visual-browser-input/v1', BROWSER_INPUT_REFERENCE);
88
100
  /**
@@ -101,7 +113,7 @@ const browserInputBranches = new Map(visualEventSchema.$defs.browserInput.oneOf.
101
113
  ]));
102
114
  const validateVisualEvent = visualValidator('visual-event/v1');
103
115
  const validateVisualResponse = visualValidator('visual-response/v1');
104
- const validateVisualHandoff = visualValidator('visual-handoff/v1');
116
+ const validateVisualHandoff = visualValidator('visual-handoff/v2');
105
117
  const validateVisualStatus = visualValidator('visual-status/v1');
106
118
  const validateVisualDiagnosticResult = visualValidator('visual-diagnostic-result/v1');
107
119
  /**
@@ -151,6 +163,85 @@ const isConfinedRelativePath = (candidate) => {
151
163
  .split('/')
152
164
  .every((segment) => segment.length > 0 && segment !== '.' && segment !== '..');
153
165
  };
166
+ /**
167
+ * Encode a native absolute path for the wire.
168
+ *
169
+ * Every filesystem path a protocol document carries is a canonical local
170
+ * `file:` URI, minted by Node's own `pathToFileURL`. A URI is invertible
171
+ * where the forward-slash string transform it replaces was not: a POSIX
172
+ * directory whose own name contains a literal backslash percent-encodes to
173
+ * `%5C` and stays distinguishable from a Windows separator, and a UNC path
174
+ * grows a non-empty host rather than passing as an ordinary POSIX-rooted
175
+ * path.
176
+ *
177
+ * Encoding cannot fail. `pathToFileURL` has no error path for an
178
+ * already-absolute native string, and every caller holds a `resolve`/`join`
179
+ * result it produced itself. A relative path reaching here is a programming
180
+ * error rather than a protocol fault, and throws as one.
181
+ */
182
+ export const toWireFileUri = (native) => {
183
+ if (!isAbsolute(native)) {
184
+ throw new Error(`A visual wire path must be absolute, not "${native}"`);
185
+ }
186
+ return pathToFileURL(native).href;
187
+ };
188
+ /**
189
+ * Decode a wire path back to native, the way untrusted input has to be read.
190
+ *
191
+ * A descriptor's bearer capabilities are never spent on a document whose path
192
+ * fields have not passed this first, so the three refusals are checked in a
193
+ * fixed order and none of them is ever quietly repaired:
194
+ *
195
+ * - `malformed` — not a parseable `file:` URI, or one `fileURLToPath` itself
196
+ * rejects (`ERR_INVALID_URL`, `ERR_INVALID_URL_SCHEME`,
197
+ * `ERR_INVALID_FILE_URL_PATH`). A bare native path lands here too: it has
198
+ * no scheme, or, for a Windows drive root, one that is not `file:`.
199
+ * - `nonlocal` — a non-empty host, which `pathToFileURL` never produces for a
200
+ * local path: `file://server/share/x` names a network share. The host is
201
+ * read off the parsed URL rather than inferred from a `fileURLToPath`
202
+ * failure, because that call refuses a foreign host on POSIX but resolves
203
+ * one to a UNC path on Windows, and reading it directly is the only way one
204
+ * URI earns the same refusal on both.
205
+ * - `noncanonical` — decodes, host is empty, but re-encoding the decoded path
206
+ * does not reproduce the input byte-for-byte. Two spellings of one target is
207
+ * the aliasing this representation exists to close, so the other spelling is
208
+ * refused rather than normalized into the canonical one. `file://localhost/x`
209
+ * lands here rather than in `nonlocal`: WHATWG `URL` normalizes a
210
+ * `localhost` file host away, leaving a URI that is simply not the spelling
211
+ * this codec mints for `/x`.
212
+ */
213
+ export const fromWireFileUri = (uri) => {
214
+ let parsed;
215
+ try {
216
+ parsed = new URL(uri);
217
+ }
218
+ catch {
219
+ return { ok: false, reason: 'malformed' };
220
+ }
221
+ if (parsed.protocol !== 'file:')
222
+ return { ok: false, reason: 'malformed' };
223
+ if (parsed.hostname !== '')
224
+ return { ok: false, reason: 'nonlocal' };
225
+ let native;
226
+ try {
227
+ native = fileURLToPath(parsed);
228
+ }
229
+ catch {
230
+ return { ok: false, reason: 'malformed' };
231
+ }
232
+ let canonical;
233
+ try {
234
+ canonical = toWireFileUri(native);
235
+ }
236
+ catch {
237
+ // `fileURLToPath` answering with something this platform does not call
238
+ // absolute is not a path this runtime can act on, whatever the URI meant.
239
+ return { ok: false, reason: 'malformed' };
240
+ }
241
+ if (canonical !== uri)
242
+ return { ok: false, reason: 'noncanonical' };
243
+ return { ok: true, value: native };
244
+ };
154
245
  const modelSemantics = (input, path, prefix) => {
155
246
  const fields = documentFields(input);
156
247
  const diagnostics = [];
@@ -237,7 +328,7 @@ const semanticsByDocument = {
237
328
  'visual-browser-input/v1': chatCarryingSemantics,
238
329
  'visual-event/v1': chatCarryingSemantics,
239
330
  'visual-response/v1': responseSemantics,
240
- 'visual-handoff/v1': handoffSemantics,
331
+ 'visual-handoff/v2': handoffSemantics,
241
332
  };
242
333
  const schemaDiagnostics = (errors, code, path) => errors.map((error) => {
243
334
  const property = error.keyword === 'additionalProperties'
@@ -1,13 +1,8 @@
1
1
  import { type VisualDiagnostic, type VisualEvent, type VisualFreezeReason, type VisualHandoff, type VisualLifecycle, type VisualSessionRequest, type VisualSessionStarted, type VisualStatus, type VisualTerminationReason } from "./protocol.js";
2
2
  import { type TerminalEventDependencies, type VisualSessionPaths } from "./session-store.js";
3
+ export { VISUAL_SERVER_DOCUMENT } from "./workspace-model.js";
3
4
  import type { VisualRenderedModel, VisualServerFrame, VisualSessionSnapshot, VisualTranscriptRecord } from "./wire.js";
4
5
  export type { VisualRenderedModel, VisualServerFrame, VisualSessionSnapshot, VisualTranscriptRecord, };
5
- /**
6
- * Document name reported by diagnostics the server itself raises — a refused
7
- * frame, a frozen queue, or a transport violation that no protocol document
8
- * owns.
9
- */
10
- export declare const VISUAL_SERVER_DOCUMENT = "visual-session-server";
11
6
  /**
12
7
  * Returned on every response. The policy admits no external origin at all, so
13
8
  * neither a compromised model nor a hostile chat message can reach the network,