yarramate 0.18.0 → 0.19.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 (65) hide show
  1. package/README.md +2 -1
  2. package/catalogues/core-enrichment.yaml +52 -1
  3. package/dist/adapters/graphify-cli.js +0 -0
  4. package/dist/adapters/likec4-cli.js +0 -0
  5. package/dist/adapters/mcp-cli.js +0 -0
  6. package/dist/adapters/visual/client.js +0 -1
  7. package/dist/adapters/visual/protocol-contract.d.ts +118 -35
  8. package/dist/adapters/visual/protocol-contract.js +1 -11
  9. package/dist/adapters/visual/protocol.d.ts +8 -1
  10. package/dist/adapters/visual/protocol.js +86 -46
  11. package/dist/adapters/visual/request.d.ts +37 -0
  12. package/dist/adapters/visual/request.js +131 -0
  13. package/dist/adapters/visual/session-server.d.ts +12 -14
  14. package/dist/adapters/visual/session-server.js +641 -283
  15. package/dist/adapters/visual/session-store.d.ts +28 -28
  16. package/dist/adapters/visual/session-store.js +32 -93
  17. package/dist/adapters/visual/wire.d.ts +36 -10
  18. package/dist/adapters/visual-cli.d.ts +3 -0
  19. package/dist/adapters/visual-cli.js +66 -1
  20. package/dist/apply-command.d.ts +10 -0
  21. package/dist/apply-command.js +399 -247
  22. package/dist/cli.js +0 -0
  23. package/dist/compiler.d.ts +13 -0
  24. package/dist/compiler.js +36 -2
  25. package/dist/graph-projection.d.ts +62 -0
  26. package/dist/graph-projection.js +222 -0
  27. package/dist/interrogate-command.d.ts +2 -0
  28. package/dist/interrogate-command.js +22 -0
  29. package/dist/kind-label.d.ts +8 -0
  30. package/dist/kind-label.js +8 -0
  31. package/dist/operations.d.ts +139 -0
  32. package/dist/operations.js +11 -0
  33. package/dist/projection.d.ts +2 -0
  34. package/dist/projection.js +8 -2
  35. package/dist/reconciliation.js +8 -7
  36. package/dist/source-document.d.ts +10 -0
  37. package/dist/source-document.js +21 -1
  38. package/dist/visual-app/assets/index-6oSR9RGM.js +354 -0
  39. package/dist/visual-app/assets/index-dwA9AOr5.css +1 -0
  40. package/dist/visual-app/index.html +2 -2
  41. package/docs/CONSUMING-YARRAMATE.md +25 -19
  42. package/package.json +6 -1
  43. package/schema/yarramate-adapter-mapping.schema.json +1 -1
  44. package/schema/yarramate-apply-result.schema.json +8 -2
  45. package/schema/yarramate-document.schema.json +1 -1
  46. package/schema/yarramate-operations.schema.json +203 -1
  47. package/schema/yarramate-projection.schema.json +12 -3
  48. package/schema/yarramate-question-catalogue.schema.json +15 -2
  49. package/schema/yarramate-visual-event.schema.json +143 -0
  50. package/schema/yarramate-visual-graph.schema.json +272 -0
  51. package/schema/yarramate-visual-layout.schema.json +41 -0
  52. package/schema/yarramate-visual-model.schema.json +6 -44
  53. package/schema/yarramate-visual-response.schema.json +170 -19
  54. package/schema/yarramate-visual-session-descriptor.schema.json +1 -1
  55. package/schema/yarramate-visual-session-request.schema.json +0 -70
  56. package/schema/yarramate-visual-session-started.schema.json +2 -9
  57. package/schema/yarramate-visual-status.schema.json +3 -2
  58. package/skills/yarramate-architecture/SKILL.md +20 -13
  59. package/skills/yarramate-architecture/references/journey-checklists.md +4 -2
  60. package/skills/yarramate-architecture/references/native-authoring.md +30 -0
  61. package/skills/yarramate-architecture/references/visual-conversations.md +149 -150
  62. package/dist/adapters/visual/likec4-compiler.d.ts +0 -55
  63. package/dist/adapters/visual/likec4-compiler.js +0 -460
  64. package/dist/visual-app/assets/index-CGvC8e3R.css +0 -1
  65. package/dist/visual-app/assets/index-CXuW1tOj.js +0 -209
package/README.md CHANGED
@@ -184,7 +184,8 @@ people, CI, skills, and agent harnesses.
184
184
 
185
185
  Optional adapters provide LikeC4 visualization from semantic projections,
186
186
  Graphify observations as evidence overlays, loopback-only visual
187
- conversations over a published protocol (beta), and separately governed
187
+ conversations that render the native model and land reviewer edits through
188
+ the same validated `apply` batch (beta), and separately governed
188
189
  compatibility profiles for external languages. Core depends on none of them.
189
190
 
190
191
  YarraMate is not affiliated with or certified by The Open Group. ArchiMate®
@@ -1,6 +1,6 @@
1
1
  format: yarramate/question-catalogue/v1
2
2
  id: core-enrichment
3
- version: "0.7"
3
+ version: "0.8"
4
4
  profile: yarramate/core@0.1
5
5
  presentation:
6
6
  title: Core enrichment interview
@@ -1075,3 +1075,54 @@ questions:
1075
1075
  resolution: >-
1076
1076
  Declare architecture states and mark presence with present-in where the
1077
1077
  distinction carries decisions; explicitly decline states otherwise.
1078
+
1079
+ - id: kind-untested
1080
+ wave: hygiene
1081
+ since: "0.8"
1082
+ scope: subject
1083
+ subjects:
1084
+ kinds:
1085
+ - yarramate/core@0.1#resource
1086
+ - yarramate/core@0.1#businessActor
1087
+ - yarramate/core@0.1#businessRole
1088
+ - yarramate/core@0.1#businessCollaboration
1089
+ - yarramate/core@0.1#businessInterface
1090
+ - yarramate/core@0.1#applicationComponent
1091
+ - yarramate/core@0.1#applicationCollaboration
1092
+ - yarramate/core@0.1#applicationInterface
1093
+ - yarramate/core@0.1#node
1094
+ - yarramate/core@0.1#device
1095
+ - yarramate/core@0.1#systemSoftware
1096
+ - yarramate/core@0.1#technologyCollaboration
1097
+ - yarramate/core@0.1#technologyInterface
1098
+ - yarramate/core@0.1#path
1099
+ - yarramate/core@0.1#communicationNetwork
1100
+ - yarramate/core@0.1#equipment
1101
+ - yarramate/core@0.1#facility
1102
+ - yarramate/core@0.1#distributionNetwork
1103
+ trigger:
1104
+ - condition: unconstrained-kind
1105
+ question: >-
1106
+ Nothing in the model tests that {subject.name} is a structural element
1107
+ at all: no behaviour is assigned to it, so reclassifying it as a
1108
+ service, an object, or a goal would compile exactly the same. What
1109
+ does it run, host, or perform?
1110
+ askPlain: >-
1111
+ What does {subject.name} actually do, or what runs on it? The model
1112
+ says what it is but nothing says what it takes part in.
1113
+ materiality: >-
1114
+ A structural kind is a promise that something behaves through this
1115
+ element. Only four relationship kinds constrain the aspect of an
1116
+ endpoint, so where none of them touches the subject the kind is a
1117
+ label no check can contradict — the choice between a node, a piece of
1118
+ system software, and an application component stops carrying
1119
+ information, and diagrams inherit a distinction the model never made.
1120
+ authority: either
1121
+ resolution: >-
1122
+ Assign {subject.name} to the behaviour it performs — the process,
1123
+ function, service, or capability it carries. Assignment is the one
1124
+ claim whose source endpoint must be an active structure, so recording
1125
+ it makes the classification falsifiable. If nothing in scope is
1126
+ assigned to it, reclassify it to a kind that does carry a claim, or
1127
+ retire it with a description saying why it sits outside this
1128
+ architecture.
File without changes
File without changes
File without changes
@@ -359,7 +359,6 @@ const localVisualStatus = async (descriptor) => {
359
359
  chat: false,
360
360
  choices: false,
361
361
  navigation: false,
362
- modelReplacement: false,
363
362
  transcript: false,
364
363
  },
365
364
  transcriptBytes,
@@ -8,7 +8,10 @@
8
8
  * contract: it can import these declarations and limits without pulling Ajv,
9
9
  * `node:path`, or the schema documents into its bundle.
10
10
  */
11
- export declare const VISUAL_PROTOCOL_VERSION: 'yarramate/visual-protocol/v1';
11
+ import type { CanvasGraph } from "../../graph-projection.js";
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/v2";
12
15
  export declare const VISUAL_LIMITS: {
13
16
  readonly messageBytes: number;
14
17
  readonly modelBytes: number;
@@ -17,9 +20,9 @@ export declare const VISUAL_LIMITS: {
17
20
  readonly reconnectMs: number;
18
21
  readonly staleSessionMs: number;
19
22
  };
20
- export type VisualAuthority = 'canonical' | 'ad-hoc';
23
+ export type VisualAuthority = "canonical";
21
24
  export interface VisualDiagnostic {
22
- readonly severity: 'error';
25
+ readonly severity: "error";
23
26
  readonly code: string;
24
27
  readonly message: string;
25
28
  readonly path: string;
@@ -28,38 +31,32 @@ export interface VisualDiagnostic {
28
31
  readonly column: number;
29
32
  }
30
33
  export interface VisualDiagnosticResult {
31
- readonly format: 'yarramate/visual-diagnostic-result/v1';
34
+ readonly format: "yarramate/visual-diagnostic-result/v1";
32
35
  readonly diagnostics: readonly VisualDiagnostic[];
33
36
  }
34
37
  export interface VisualCapabilities {
35
38
  readonly chat: boolean;
36
39
  readonly choices: boolean;
37
40
  readonly navigation: boolean;
38
- readonly modelReplacement: boolean;
39
41
  readonly transcript: boolean;
40
42
  }
41
43
  export interface VisualModel {
42
- readonly format: 'yarramate/visual-model/v1';
44
+ readonly format: "yarramate/visual-model/v1";
43
45
  readonly authority: VisualAuthority;
44
46
  readonly initialView: string;
45
47
  readonly sourceDigests: Readonly<Record<string, string>>;
46
- readonly files: Readonly<Record<string, string>>;
47
- }
48
- export interface VisualCompilerCommand {
49
- readonly command: string;
50
- readonly args: readonly string[];
48
+ readonly graph: CanvasGraph;
51
49
  }
52
50
  export interface VisualSessionRequest {
53
- readonly format: 'yarramate/visual-session-request/v1';
54
- readonly authority: VisualModel['authority'];
51
+ readonly format: "yarramate/visual-session-request/v1";
52
+ readonly authority: VisualModel["authority"];
55
53
  readonly title: string;
56
54
  readonly description: string;
57
55
  readonly chatEnabled: boolean;
58
- readonly compiler: VisualCompilerCommand;
59
56
  readonly initialModel: VisualModel;
60
57
  }
61
58
  export interface VisualSessionStarted {
62
- readonly format: 'yarramate/visual-session-started/v1';
59
+ readonly format: "yarramate/visual-session-started/v1";
63
60
  readonly protocolVersion: typeof VISUAL_PROTOCOL_VERSION;
64
61
  readonly sessionId: string;
65
62
  readonly authority: VisualAuthority;
@@ -74,7 +71,7 @@ export interface VisualSessionStarted {
74
71
  readonly startedAt: string;
75
72
  }
76
73
  export interface VisualSessionDescriptor {
77
- readonly format: 'yarramate/visual-session-descriptor/v1';
74
+ readonly format: "yarramate/visual-session-descriptor/v1";
78
75
  readonly protocolVersion: typeof VISUAL_PROTOCOL_VERSION;
79
76
  readonly sessionId: string;
80
77
  readonly origin: string;
@@ -94,6 +91,44 @@ export interface VisualViewNavigatePayload {
94
91
  readonly viewId: string;
95
92
  readonly requiresAttention: boolean;
96
93
  }
94
+ export interface VisualViewSummary {
95
+ readonly id: string;
96
+ readonly title: string;
97
+ readonly description: string;
98
+ readonly query: ProjectionQuery;
99
+ readonly presentation: ProjectionDefinition["presentation"];
100
+ }
101
+ export interface VisualKindOption {
102
+ readonly id: string;
103
+ readonly label: string;
104
+ }
105
+ export interface VisualFilterQueryPayload {
106
+ readonly query: ProjectionQuery;
107
+ }
108
+ export interface VisualFilterResultPayload {
109
+ readonly query: ProjectionQuery;
110
+ readonly matchedIds: readonly string[];
111
+ }
112
+ export interface VisualViewSavePayload {
113
+ readonly id?: string;
114
+ readonly title: string;
115
+ readonly description: string;
116
+ readonly query: ProjectionQuery;
117
+ readonly presentation: ProjectionDefinition["presentation"];
118
+ }
119
+ export interface VisualLayoutPositions {
120
+ readonly [subjectId: string]: {
121
+ readonly x: number;
122
+ readonly y: number;
123
+ };
124
+ }
125
+ export interface VisualChangesetCommitPayload {
126
+ readonly operations: readonly YarramateOperation[];
127
+ }
128
+ export interface VisualLayoutSavePayload {
129
+ readonly projectionId: string;
130
+ readonly positions: VisualLayoutPositions;
131
+ }
97
132
  /**
98
133
  * Terminal event payload. Every reason is the runtime's to choose: only it
99
134
  * knows whether a session ended by request, by a failing child, by a browser
@@ -104,7 +139,7 @@ export interface VisualSessionEndPayload {
104
139
  }
105
140
  /** End as an untrusted browser may ask for it, and nothing more. */
106
141
  export interface VisualBrowserSessionEndPayload {
107
- readonly reason: 'user-ended';
142
+ readonly reason: "user-ended";
108
143
  }
109
144
  export interface VisualBrowserConnectedPayload {
110
145
  readonly connectionId: string;
@@ -125,24 +160,40 @@ export interface VisualBrowserDisconnectedPayload {
125
160
  * sequence and event identifier.
126
161
  */
127
162
  export type VisualBrowserInput = {
128
- readonly type: 'chat.message';
163
+ readonly type: "chat.message";
129
164
  readonly lastAcknowledgedSequence: number;
130
165
  readonly payload: VisualChatMessagePayload;
131
166
  } | {
132
- readonly type: 'choice.selected';
167
+ readonly type: "choice.selected";
133
168
  readonly lastAcknowledgedSequence: number;
134
169
  readonly payload: VisualChoiceSelectedPayload;
135
170
  } | {
136
- readonly type: 'view.navigate';
171
+ readonly type: "view.navigate";
137
172
  readonly lastAcknowledgedSequence: number;
138
173
  readonly payload: VisualViewNavigatePayload;
139
174
  } | {
140
- readonly type: 'session.end';
175
+ readonly type: "filter.query";
176
+ readonly lastAcknowledgedSequence: number;
177
+ readonly payload: VisualFilterQueryPayload;
178
+ } | {
179
+ readonly type: "view.save";
180
+ readonly lastAcknowledgedSequence: number;
181
+ readonly payload: VisualViewSavePayload;
182
+ } | {
183
+ readonly type: "changeset.commit";
184
+ readonly lastAcknowledgedSequence: number;
185
+ readonly payload: VisualChangesetCommitPayload;
186
+ } | {
187
+ readonly type: "layout.save";
188
+ readonly lastAcknowledgedSequence: number;
189
+ readonly payload: VisualLayoutSavePayload;
190
+ } | {
191
+ readonly type: "session.end";
141
192
  readonly lastAcknowledgedSequence: number;
142
193
  readonly payload: VisualBrowserSessionEndPayload;
143
194
  };
144
195
  interface VisualEventEnvelope<Type extends string, Payload> {
145
- readonly format: 'yarramate/visual-event/v1';
196
+ readonly format: "yarramate/visual-event/v1";
146
197
  readonly sessionId: string;
147
198
  readonly sequence: number;
148
199
  readonly eventId: string;
@@ -150,12 +201,25 @@ interface VisualEventEnvelope<Type extends string, Payload> {
150
201
  readonly timestamp: string;
151
202
  readonly payload: Payload;
152
203
  }
153
- 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>;
204
+ 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>;
205
+ /**
206
+ * The filter a chat turn resolved to. The agent states `query` and nothing
207
+ * else: the runtime evaluates it against the same graph a `filter.query`
208
+ * event is evaluated against and fills `matchedIds` in before the response
209
+ * is journaled or streamed (ADR 0090). `matchedIds` is optional so a
210
+ * resolved response survives a transcript round-trip - an agent that sends
211
+ * one is refused.
212
+ */
213
+ export interface VisualChatAppliedQuery {
214
+ readonly query: ProjectionQuery;
215
+ readonly matchedIds?: readonly string[];
216
+ }
154
217
  export interface VisualChatResponsePayload {
155
218
  readonly text: string;
219
+ readonly appliedQuery?: VisualChatAppliedQuery;
156
220
  }
157
221
  export interface VisualAgentStatusPayload {
158
- readonly state: 'thinking' | 'compiling' | 'waiting' | 'idle';
222
+ readonly state: "thinking" | "compiling" | "waiting" | "idle";
159
223
  readonly detail?: string;
160
224
  }
161
225
  export interface VisualChoiceOption {
@@ -168,9 +232,6 @@ export interface VisualChoicePresentPayload {
168
232
  readonly question: string;
169
233
  readonly options: readonly VisualChoiceOption[];
170
234
  }
171
- export interface VisualModelReplacePayload {
172
- readonly model: VisualModel;
173
- }
174
235
  export interface VisualHandoffSummary {
175
236
  readonly summary: string;
176
237
  readonly confirmedDecisions: readonly string[];
@@ -181,8 +242,30 @@ export interface VisualHandoffSummary {
181
242
  export interface VisualDiagnosticPayload {
182
243
  readonly diagnostics: readonly VisualDiagnostic[];
183
244
  }
245
+ export type VisualViewSaveResultPayload = {
246
+ readonly ok: true;
247
+ readonly id: string;
248
+ readonly path: string;
249
+ } | {
250
+ readonly ok: false;
251
+ readonly diagnostics: readonly VisualDiagnostic[];
252
+ };
253
+ export type VisualApplyResultPayload = {
254
+ readonly ok: true;
255
+ readonly result: YarramateApplyResult;
256
+ } | {
257
+ readonly ok: false;
258
+ readonly diagnostics: readonly VisualDiagnostic[];
259
+ };
260
+ export type VisualLayoutSaveResultPayload = {
261
+ readonly ok: true;
262
+ readonly path: string;
263
+ } | {
264
+ readonly ok: false;
265
+ readonly message: string;
266
+ };
184
267
  interface VisualResponseEnvelope<Type extends string, Payload> {
185
- readonly format: 'yarramate/visual-response/v1';
268
+ readonly format: "yarramate/visual-response/v1";
186
269
  readonly sessionId: string;
187
270
  readonly responseId: string;
188
271
  readonly eventId: string;
@@ -190,11 +273,11 @@ interface VisualResponseEnvelope<Type extends string, Payload> {
190
273
  readonly timestamp: string;
191
274
  readonly payload: Payload;
192
275
  }
193
- export type VisualResponse = VisualResponseEnvelope<'chat.response', VisualChatResponsePayload> | VisualResponseEnvelope<'agent.status', VisualAgentStatusPayload> | VisualResponseEnvelope<'choice.present', VisualChoicePresentPayload> | VisualResponseEnvelope<'model.replace', VisualModelReplacePayload> | VisualResponseEnvelope<'handoff.complete', VisualHandoffSummary> | VisualResponseEnvelope<'diagnostic', VisualDiagnosticPayload>;
194
- export type VisualHandoffDecision = 'completed' | 'cancelled' | 'failed';
195
- export type VisualTerminationReason = 'user-ended' | 'child-failed' | 'browser-timeout' | 'main-cancelled' | 'server-failed' | 'compiler-failed';
276
+ export type VisualResponse = VisualResponseEnvelope<"chat.response", VisualChatResponsePayload> | VisualResponseEnvelope<"agent.status", VisualAgentStatusPayload> | VisualResponseEnvelope<"choice.present", VisualChoicePresentPayload> | VisualResponseEnvelope<"handoff.complete", VisualHandoffSummary> | VisualResponseEnvelope<"diagnostic", VisualDiagnosticPayload>;
277
+ export type VisualHandoffDecision = "completed" | "cancelled" | "failed";
278
+ export type VisualTerminationReason = "user-ended" | "child-failed" | "browser-timeout" | "main-cancelled" | "server-failed" | "compiler-failed";
196
279
  export interface VisualHandoff extends VisualHandoffSummary {
197
- readonly format: 'yarramate/visual-handoff/v1';
280
+ readonly format: "yarramate/visual-handoff/v1";
198
281
  readonly sessionId: string;
199
282
  readonly authority: VisualAuthority;
200
283
  readonly decision: VisualHandoffDecision;
@@ -204,10 +287,10 @@ export interface VisualHandoff extends VisualHandoffSummary {
204
287
  readonly transcript?: readonly (VisualEvent | VisualResponse)[];
205
288
  readonly completedAt: string;
206
289
  }
207
- export type VisualLifecycle = 'starting' | 'running' | 'draining' | 'stopped';
208
- export type VisualFreezeReason = 'message-bytes' | 'model-bytes' | 'transcript-bytes' | 'pending-events' | 'browser-disconnected' | 'terminal-event';
290
+ export type VisualLifecycle = "starting" | "running" | "draining" | "stopped";
291
+ export type VisualFreezeReason = "message-bytes" | "model-bytes" | "transcript-bytes" | "pending-events" | "browser-disconnected" | "terminal-event" | "recompile-failed";
209
292
  export interface VisualStatus {
210
- readonly format: 'yarramate/visual-status/v1';
293
+ readonly format: "yarramate/visual-status/v1";
211
294
  readonly protocolVersion: typeof VISUAL_PROTOCOL_VERSION;
212
295
  readonly sessionId: string;
213
296
  readonly lifecycle: VisualLifecycle;
@@ -1,14 +1,4 @@
1
- /**
2
- * Every visual protocol declaration, and the limits they are measured against,
3
- * with no runtime dependency of any kind.
4
- *
5
- * The validators that police these documents live in `./protocol.js`, which
6
- * re-exports this module so a Node consumer still has one import for the whole
7
- * protocol. The split exists because the browser application ships the same
8
- * contract: it can import these declarations and limits without pulling Ajv,
9
- * `node:path`, or the schema documents into its bundle.
10
- */
11
- export const VISUAL_PROTOCOL_VERSION = 'yarramate/visual-protocol/v1';
1
+ export const VISUAL_PROTOCOL_VERSION = "yarramate/visual-protocol/v2";
12
2
  export const VISUAL_LIMITS = {
13
3
  messageBytes: 64 * 1024,
14
4
  modelBytes: 5 * 1024 * 1024,
@@ -1,10 +1,17 @@
1
1
  import { type ParseResult, type VisualBrowserInput, type VisualDiagnosticResult, type VisualEvent, type VisualHandoff, type VisualModel, type VisualResponse, type VisualSessionDescriptor, type VisualSessionRequest, type VisualSessionStarted, type VisualStatus } from './protocol-contract.js';
2
2
  export * from './protocol-contract.js';
3
- export declare const isSafeVisualModelPath: (candidate: string) => boolean;
4
3
  export declare const parseVisualModel: (input: unknown) => ParseResult<VisualModel>;
5
4
  export declare const parseVisualSessionRequest: (input: unknown) => ParseResult<VisualSessionRequest>;
6
5
  export declare const parseVisualSessionStarted: (input: unknown) => ParseResult<VisualSessionStarted>;
7
6
  export declare const parseVisualSessionDescriptor: (input: unknown) => ParseResult<VisualSessionDescriptor>;
7
+ /**
8
+ * The type an untrusted frame claims to be, when the protocol has that type.
9
+ *
10
+ * A refusal has to say what it refused, and the browser is holding a control
11
+ * open until it does. The claim is worth reporting before the document is
12
+ * known to be valid because the frame that named it is the one that failed.
13
+ */
14
+ export declare const visualBrowserInputType: (input: unknown) => VisualBrowserInput['type'] | undefined;
8
15
  export declare const parseVisualBrowserInput: (input: unknown) => ParseResult<VisualBrowserInput>;
9
16
  export declare const parseVisualEvent: (input: unknown) => ParseResult<VisualEvent>;
10
17
  export declare const parseVisualResponse: (input: unknown) => ParseResult<VisualResponse>;
@@ -1,12 +1,22 @@
1
1
  import { posix } from 'node:path';
2
2
  import Ajv2020Module from 'ajv/dist/2020.js';
3
- import { describeSchemaViolation } from '../../source-document.js';
3
+ import { describeSchemaViolation, readableSchemaErrors, } from '../../source-document.js';
4
4
  import visualDiagnosticResultSchema from '../../../schema/yarramate-visual-diagnostic-result.schema.json' with { type: 'json'
5
5
  };
6
6
  import visualEventSchema from '../../../schema/yarramate-visual-event.schema.json' with { type: 'json'
7
7
  };
8
+ import visualGraphSchema from '../../../schema/yarramate-visual-graph.schema.json' with { type: 'json'
9
+ };
10
+ import applyResultSchema from '../../../schema/yarramate-apply-result.schema.json' with { type: 'json'
11
+ };
12
+ import operationsSchema from '../../../schema/yarramate-operations.schema.json' with { type: 'json'
13
+ };
14
+ import projectionSchema from '../../../schema/yarramate-projection.schema.json' with { type: 'json'
15
+ };
8
16
  import visualHandoffSchema from '../../../schema/yarramate-visual-handoff.schema.json' with { type: 'json'
9
17
  };
18
+ import visualLayoutSchema from '../../../schema/yarramate-visual-layout.schema.json' with { type: 'json'
19
+ };
10
20
  import visualModelSchema from '../../../schema/yarramate-visual-model.schema.json' with { type: 'json'
11
21
  };
12
22
  import visualResponseSchema from '../../../schema/yarramate-visual-response.schema.json' with { type: 'json'
@@ -24,17 +34,28 @@ import { VISUAL_LIMITS, } from './protocol-contract.js';
24
34
  // validates travel with the validators.
25
35
  export * from './protocol-contract.js';
26
36
  const Ajv2020 = Ajv2020Module.default;
27
- const ajv = new Ajv2020({ allErrors: true });
37
+ // `discriminator` on Core's operation union routes a staged edit to the branch
38
+ // its `op` names, so a browser changeset reports the fault the reviewer made
39
+ // rather than one near-miss per operation kind.
40
+ const ajv = new Ajv2020({ allErrors: true, discriminator: true });
28
41
  ajv.addSchema([
29
42
  visualDiagnosticResultSchema,
30
- visualModelSchema,
31
43
  visualEventSchema,
44
+ visualGraphSchema,
45
+ visualHandoffSchema,
46
+ visualModelSchema,
32
47
  visualResponseSchema,
48
+ visualSessionDescriptorSchema,
33
49
  visualSessionRequestSchema,
34
50
  visualSessionStartedSchema,
35
- visualSessionDescriptorSchema,
36
- visualHandoffSchema,
37
51
  visualStatusSchema,
52
+ visualLayoutSchema,
53
+ projectionSchema,
54
+ // The commit path's event and response documents reference Core's operations
55
+ // and apply-result shapes rather than restating them, so Ajv needs both
56
+ // resolvable before it compiles the visual validators.
57
+ operationsSchema,
58
+ applyResultSchema,
38
59
  ]);
39
60
  // Diagnostics report the document they came from rather than a source file,
40
61
  // because visual protocol documents arrive as parsed JSON over the wire.
@@ -50,7 +71,22 @@ const validateVisualModel = visualValidator('visual-model/v1');
50
71
  const validateVisualSessionRequest = visualValidator('visual-session-request/v1');
51
72
  const validateVisualSessionStarted = visualValidator('visual-session-started/v1');
52
73
  const validateVisualSessionDescriptor = visualValidator('visual-session-descriptor/v1');
53
- const validateVisualBrowserInput = visualValidator('visual-browser-input/v1', 'https://yarramate.org/schema/visual-event/v1#/$defs/browserInput');
74
+ const BROWSER_INPUT_REFERENCE = 'https://yarramate.org/schema/visual-event/v1#/$defs/browserInput';
75
+ const validateVisualBrowserInput = visualValidator('visual-browser-input/v1', BROWSER_INPUT_REFERENCE);
76
+ /**
77
+ * One validator per input type, keyed by the `type` its branch fixes.
78
+ *
79
+ * The union reports every branch it tried, so a `changeset.commit` that gets
80
+ * one field wrong arrives as that one violation buried under seven other
81
+ * types' missing properties - unreadable, and it is the reviewer who has to
82
+ * read it. An input that names a type the protocol knows is answered against
83
+ * that type alone. The vocabulary is read off the schema rather than restated
84
+ * here, so a new input type cannot be added without one.
85
+ */
86
+ const browserInputBranches = new Map(visualEventSchema.$defs.browserInput.oneOf.map((branch, index) => [
87
+ branch.properties.type.const,
88
+ visualValidator('visual-browser-input/v1', `${BROWSER_INPUT_REFERENCE}/oneOf/${index}`),
89
+ ]));
54
90
  const validateVisualEvent = visualValidator('visual-event/v1');
55
91
  const validateVisualResponse = visualValidator('visual-response/v1');
56
92
  const validateVisualHandoff = visualValidator('visual-handoff/v1');
@@ -83,13 +119,12 @@ const jsonBytes = (value) => {
83
119
  return 0;
84
120
  return Buffer.byteLength(encoded, 'utf8');
85
121
  };
86
- const MODEL_ROOT_CONFIG = 'likec4.config.json';
87
- const MODEL_SOURCE_EXTENSIONS = ['.c4', '.likec4'];
88
122
  /**
89
123
  * Relative POSIX path confined to the candidate root. Absolute paths, parent
90
- * traversal, empty segments, dot-prefixed segments (how a staged symlink or
91
- * metadata entry would be smuggled in), Windows separators, and NUL bytes are
92
- * all rejected.
124
+ * traversal, empty segments, `.`/`..` segments, Windows separators, and NUL
125
+ * bytes are all rejected. A dot-prefixed directory is not an escape and is
126
+ * kept: every workspace keeps its documents under `.yarramate/`, so the
127
+ * digests of a canonical model are unrepresentable without it.
93
128
  */
94
129
  const isConfinedRelativePath = (candidate) => {
95
130
  if (candidate.length === 0 || candidate.length > 1024)
@@ -102,35 +137,11 @@ const isConfinedRelativePath = (candidate) => {
102
137
  return false;
103
138
  return candidate
104
139
  .split('/')
105
- .every((segment) => segment.length > 0 && !segment.startsWith('.'));
106
- };
107
- export const isSafeVisualModelPath = (candidate) => {
108
- if (!isConfinedRelativePath(candidate))
109
- return false;
110
- if (candidate === MODEL_ROOT_CONFIG)
111
- return true;
112
- const extension = posix.extname(candidate);
113
- return MODEL_SOURCE_EXTENSIONS.some((allowed) => allowed === extension);
140
+ .every((segment) => segment.length > 0 && segment !== '.' && segment !== '..');
114
141
  };
115
142
  const modelSemantics = (input, path, prefix) => {
116
143
  const fields = documentFields(input);
117
144
  const diagnostics = [];
118
- const files = documentFields(fields.files);
119
- const fileKeys = Object.keys(files);
120
- if (fileKeys.length > 0) {
121
- let sources = 0;
122
- for (const key of fileKeys) {
123
- if (!isSafeVisualModelPath(key)) {
124
- diagnostics.push(visualDiagnostic('YMVS113', `Model file "${key}" escapes the candidate root or is not a LikeC4 source`, path, `${prefix}/files/${pointerSegment(key)}`));
125
- continue;
126
- }
127
- if (key !== MODEL_ROOT_CONFIG)
128
- sources += 1;
129
- }
130
- if (sources === 0) {
131
- diagnostics.push(visualDiagnostic('YMVS114', 'A visual model requires at least one .c4 or .likec4 source file', path, `${prefix}/files`));
132
- }
133
- }
134
145
  const digestKeys = Object.keys(documentFields(fields.sourceDigests));
135
146
  for (const key of digestKeys) {
136
147
  if (isConfinedRelativePath(key))
@@ -140,9 +151,6 @@ const modelSemantics = (input, path, prefix) => {
140
151
  if (fields.authority === 'canonical' && digestKeys.length === 0) {
141
152
  diagnostics.push(visualDiagnostic('YMVS112', 'A canonical visual model must record the source digests it was derived from', path, `${prefix}/sourceDigests`));
142
153
  }
143
- if (fields.authority === 'ad-hoc' && digestKeys.length > 0) {
144
- diagnostics.push(visualDiagnostic('YMVS112', 'An ad-hoc visual model must not claim canonical source digests', path, `${prefix}/sourceDigests`));
145
- }
146
154
  if (jsonBytes(input) > VISUAL_LIMITS.modelBytes) {
147
155
  diagnostics.push(visualDiagnostic('YMVS115', `A visual model must not exceed ${VISUAL_LIMITS.modelBytes} bytes`, path, prefix === '' ? '/' : prefix));
148
156
  }
@@ -182,9 +190,6 @@ const responseSemantics = (input, path) => {
182
190
  if (fields.type === 'chat.response') {
183
191
  return chatTextSemantics(fields.payload, path, '/payload');
184
192
  }
185
- if (fields.type === 'model.replace') {
186
- return modelSemantics(documentFields(fields.payload).model, path, '/payload/model');
187
- }
188
193
  return [];
189
194
  };
190
195
  /**
@@ -233,15 +238,30 @@ const schemaDiagnostics = (errors, code, path) => errors.map((error) => {
233
238
  ? `Property "${property}" is not allowed`
234
239
  : `${path} schema violation: ${describeSchemaViolation(error)}`, path, pointer);
235
240
  });
241
+ /**
242
+ * The same violation, reported once.
243
+ *
244
+ * A union reports the branches it tried, and sibling branches that share a
245
+ * requirement each raise it - the reviewer reads one document, not the
246
+ * validator's search. Diagnostics are already sorted, so identical neighbours
247
+ * are adjacent; nothing that differs in code, pointer, or message is lost.
248
+ */
249
+ const distinct = (diagnostics) => diagnostics.filter((diagnostic, index) => {
250
+ if (index === 0)
251
+ return true;
252
+ const previous = diagnostics[index - 1];
253
+ return (previous === undefined ||
254
+ visualDiagnosticOrder(previous, diagnostic) !== 0);
255
+ });
236
256
  const parseWith = (validate, input, code) => {
237
257
  const path = documentPaths.get(validate) ?? 'visual-document';
238
258
  const semantics = semanticsByDocument[path];
239
- const diagnostics = [
259
+ const diagnostics = distinct([
240
260
  ...(validate(input)
241
261
  ? []
242
- : schemaDiagnostics(validate.errors ?? [], code, path)),
262
+ : schemaDiagnostics(readableSchemaErrors(validate.errors ?? []), code, path)),
243
263
  ...(semantics ? semantics(input, path) : []),
244
- ].sort(visualDiagnosticOrder);
264
+ ].sort(visualDiagnosticOrder));
245
265
  if (diagnostics.length > 0)
246
266
  return { ok: false, diagnostics };
247
267
  return { ok: true, value: input };
@@ -250,7 +270,27 @@ export const parseVisualModel = (input) => parseWith(validateVisualModel, input,
250
270
  export const parseVisualSessionRequest = (input) => parseWith(validateVisualSessionRequest, input, 'YMVS101');
251
271
  export const parseVisualSessionStarted = (input) => parseWith(validateVisualSessionStarted, input, 'YMVS102');
252
272
  export const parseVisualSessionDescriptor = (input) => parseWith(validateVisualSessionDescriptor, input, 'YMVS103');
253
- export const parseVisualBrowserInput = (input) => parseWith(validateVisualBrowserInput, input, 'YMVS109');
273
+ /**
274
+ * The type an untrusted frame claims to be, when the protocol has that type.
275
+ *
276
+ * A refusal has to say what it refused, and the browser is holding a control
277
+ * open until it does. The claim is worth reporting before the document is
278
+ * known to be valid because the frame that named it is the one that failed.
279
+ */
280
+ export const visualBrowserInputType = (input) => {
281
+ const claimed = documentFields(input).type;
282
+ return typeof claimed === 'string' && browserInputBranches.has(claimed)
283
+ ? claimed
284
+ : undefined;
285
+ };
286
+ export const parseVisualBrowserInput = (input) => {
287
+ const claimed = visualBrowserInputType(input);
288
+ const validate = claimed === undefined
289
+ ? validateVisualBrowserInput
290
+ : // The branch is in the map because `claimed` came out of it.
291
+ browserInputBranches.get(claimed);
292
+ return parseWith(validate, input, 'YMVS109');
293
+ };
254
294
  export const parseVisualEvent = (input) => parseWith(validateVisualEvent, input, 'YMVS104');
255
295
  export const parseVisualResponse = (input) => parseWith(validateVisualResponse, input, 'YMVS105');
256
296
  export const parseVisualHandoff = (input) => parseWith(validateVisualHandoff, input, 'YMVS107');
@@ -0,0 +1,37 @@
1
+ import type { VisualDiagnostic, VisualSessionRequest } from './protocol-contract.js';
2
+ export interface VisualSessionRequestOptions {
3
+ readonly cwd: string;
4
+ /** Defaults to the workspace id. */
5
+ readonly title?: string;
6
+ /** Defaults to a line naming the workspace and its manifest. */
7
+ readonly description?: string;
8
+ /** Defaults to the first projection the workspace declares. */
9
+ readonly initialView?: string;
10
+ /** Defaults to `false`: a diagram-only session needs no model provider. */
11
+ readonly chatEnabled?: boolean;
12
+ }
13
+ export type VisualSessionRequestResult = {
14
+ readonly ok: true;
15
+ readonly request: VisualSessionRequest;
16
+ } | {
17
+ readonly ok: false;
18
+ readonly diagnostics: readonly VisualDiagnostic[];
19
+ };
20
+ /**
21
+ * Builds the `yarramate/visual-session-request/v1` document that `start`
22
+ * consumes, from the workspace on disk.
23
+ *
24
+ * The request carries a whole compiled model — 247 nodes and 338 edges for this
25
+ * repository's own architecture — so it is a machine's transcription of the
26
+ * workspace, never something an agent can honestly hand-author. Every input the
27
+ * document needs is already derivable: the graph from the native compiler, the
28
+ * digests from the same sources that compiled, and the view list from the
29
+ * workspace's declared projections.
30
+ *
31
+ * The result is validated through `parseVisualSessionRequest` before it is
32
+ * returned, so the byte ceiling, digest confinement, and authority agreement
33
+ * are the protocol's own rules rather than a second set restated here. A
34
+ * builder that cannot produce a valid document refuses instead of handing a
35
+ * session something it would reject at start.
36
+ */
37
+ export declare const buildVisualSessionRequest: (options: VisualSessionRequestOptions) => VisualSessionRequestResult;