yarramate 1.1.0 → 1.2.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.
@@ -37,6 +37,7 @@ const selectStep = (report, subjectFilter, askPlainById) => {
37
37
  : { askPlain: askPlainTemplate.trim() }),
38
38
  materiality: question.materiality,
39
39
  resolution: question.resolution,
40
+ trigger: question.trigger,
40
41
  ...(question.since === undefined ? {} : { since: question.since }),
41
42
  };
42
43
  }
@@ -57,6 +58,7 @@ const selectStep = (report, subjectFilter, askPlainById) => {
57
58
  : { askPlain: renderQuestion(askPlainTemplate, first.id, first.name) }),
58
59
  materiality: question.materiality,
59
60
  resolution: question.resolution,
61
+ trigger: question.trigger,
60
62
  ...(question.since === undefined ? {} : { since: question.since }),
61
63
  subject: {
62
64
  id: first.id,
@@ -74,6 +76,56 @@ const selectStep = (report, subjectFilter, askPlainById) => {
74
76
  }
75
77
  return null;
76
78
  };
79
+ const localKind = (qualified) => {
80
+ const hash = qualified.lastIndexOf('#');
81
+ return hash === -1 ? qualified : qualified.slice(hash + 1);
82
+ };
83
+ const skeletonHeader = (documentAddress, op) => [
84
+ '',
85
+ 'Prefilled skeleton (edit the <placeholders>, save as operations.yaml):',
86
+ ' format: yarramate/operations/v1',
87
+ ' operations:',
88
+ ` - op: ${op}`,
89
+ ` document: ${documentAddress}`,
90
+ ];
91
+ // The skeleton is a rendering of the step's trigger (#289), printed only
92
+ // when a single condition maps unambiguously onto one operation, so a
93
+ // wrong skeleton is never offered; every other trigger leaves the output
94
+ // exactly as before. Kinds print as local names: that is the form a
95
+ // native document declares.
96
+ const renderSkeleton = (step, documentAddress) => {
97
+ if (documentAddress === undefined || step.trigger.length !== 1)
98
+ return [];
99
+ const condition = step.trigger[0];
100
+ if (condition.condition === 'no-subject-of-kind') {
101
+ const kinds = condition.kinds.map(localKind);
102
+ const alternatives = kinds.length > 1 ? ` # or: ${kinds.slice(1).join(', ')}` : '';
103
+ return [
104
+ ...skeletonHeader(documentAddress, 'add-concept'),
105
+ ' concept:',
106
+ ' id: <kebab-case-id>',
107
+ ` kind: ${kinds[0]}${alternatives}`,
108
+ ' name: <one line>',
109
+ ];
110
+ }
111
+ if (condition.condition === 'missing-relationship' &&
112
+ step.subject !== undefined) {
113
+ const kinds = condition.kinds.map(localKind);
114
+ const alternatives = kinds.length > 1 ? ` # or: ${kinds.slice(1).join(', ')}` : '';
115
+ const swap = condition.direction === 'any' ? ' # or swap the endpoints' : '';
116
+ const from = condition.direction === 'incoming' ? '<counterpart-id>' : step.subject.id;
117
+ const to = condition.direction === 'incoming' ? step.subject.id : '<counterpart-id>';
118
+ return [
119
+ ...skeletonHeader(documentAddress, 'add-relationship'),
120
+ ' relationship:',
121
+ ' id: <kebab-case-id>',
122
+ ` kind: ${kinds[0]}${alternatives}`,
123
+ ` from: ${from}${swap}`,
124
+ ` to: ${to}`,
125
+ ];
126
+ }
127
+ return [];
128
+ };
77
129
  export function runDesignCommand(options, cwd) {
78
130
  const json = options.includes('--json');
79
131
  // Facilitation is a rendering preference, not an interview mode: the
@@ -232,7 +284,21 @@ export function runDesignCommand(options, cwd) {
232
284
  if (slice !== undefined) {
233
285
  lines.push('', 'Subject slice:', '', slice.trimEnd());
234
286
  }
235
- lines.push('', 'Answer by updating the model (one atomic batch):', ` yarramate apply <operations.yaml> ${workspacePath}`, `Then re-run: yarramate design ${workspacePath}`);
287
+ // The skeleton's document address is the manifest-relative form
288
+ // when the first document sits under the manifest directory - the
289
+ // address an author naturally writes and apply accepts (#216) -
290
+ // falling back to the workspace path, which apply also accepts.
291
+ const manifestDirectory = workspacePath.includes('/')
292
+ ? workspacePath.slice(0, workspacePath.lastIndexOf('/'))
293
+ : '';
294
+ const firstDocument = workspace.documents[0];
295
+ const documentAddress = firstDocument === undefined
296
+ ? undefined
297
+ : manifestDirectory !== '' &&
298
+ firstDocument.startsWith(`${manifestDirectory}/`)
299
+ ? firstDocument.slice(manifestDirectory.length + 1)
300
+ : firstDocument;
301
+ lines.push('', 'Answer by updating the model (one atomic batch):', ` yarramate apply <operations.yaml> ${workspacePath}`, ...renderSkeleton(step, documentAddress), `Then re-run: yarramate design ${workspacePath}`);
236
302
  }
237
303
  return { exitCode: 0, stdout: `${lines.join('\n')}\n`, stderr: '' };
238
304
  }
@@ -128,6 +128,14 @@ export interface ReportQuestion {
128
128
  readonly question: string;
129
129
  readonly materiality: string;
130
130
  readonly resolution: string;
131
+ /**
132
+ * The catalogue trigger, verbatim (#289). The conditions that opened a
133
+ * question are its machine-readable answer shape: a host builds the
134
+ * matching affordance (a prefilled form, an operations skeleton) from
135
+ * them instead of re-deriving the shape from its own catalogue copy and
136
+ * drifting from engine semantics.
137
+ */
138
+ readonly trigger: readonly CatalogueCondition[];
131
139
  readonly since?: string;
132
140
  readonly subjects?: readonly OpenSubject[];
133
141
  }
@@ -443,6 +443,7 @@ export function evaluateCatalogue(catalogue, graph, profileContext) {
443
443
  question: question.question.trim(),
444
444
  materiality: question.materiality.trim(),
445
445
  resolution: question.resolution.trim(),
446
+ trigger: question.trigger,
446
447
  ...(question.since === undefined ? {} : { since: question.since }),
447
448
  };
448
449
  if (question.scope === 'workspace') {
@@ -268,6 +268,17 @@ answer is about the model and belongs in front of a user, different means the
268
268
  engine moved and the right response is to re-baseline silently rather than
269
269
  reopen someone's queue.
270
270
 
271
+ Every question also carries `trigger`, the catalogue conditions that opened
272
+ it, verbatim ([ADR 0110](adr/0110-an-open-question-carries-its-answer-shape.md)).
273
+ That is the question's machine-readable answer shape: a host building an
274
+ answering affordance — a concept form with the kind preselected from
275
+ `no-subject-of-kind`, a relationship editor with one endpoint fixed by
276
+ `missing-relationship`'s `direction`, an attestation form on
277
+ `missing-attestation`'s `topic` — maps the conditions directly instead of
278
+ re-deriving the shape from its own catalogue copy. The field is required and
279
+ the published report schema uses `additionalProperties: false`, so upgrade a
280
+ separately pinned schema together with the package.
281
+
271
282
  ### Mount the visual editor
272
283
 
273
284
  Mount the packaged editor when the consuming product owns the sources and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -83,7 +83,8 @@
83
83
  "authority",
84
84
  "question",
85
85
  "materiality",
86
- "resolution"
86
+ "resolution",
87
+ "trigger"
87
88
  ],
88
89
  "properties": {
89
90
  "questionId": {
@@ -123,6 +124,14 @@
123
124
  "type": "string",
124
125
  "minLength": 1
125
126
  },
127
+ "trigger": {
128
+ "description": "The catalogue trigger, verbatim: the conditions that opened this question, i.e. its machine-readable answer shape.",
129
+ "type": "array",
130
+ "minItems": 1,
131
+ "items": {
132
+ "$ref": "#/$defs/condition"
133
+ }
134
+ },
126
135
  "subject": {
127
136
  "type": "object",
128
137
  "additionalProperties": false,
@@ -163,5 +172,377 @@
163
172
  "type": "string",
164
173
  "minLength": 1
165
174
  }
175
+ },
176
+ "$defs": {
177
+ "condition": {
178
+ "description": "One deterministic trigger condition. All conditions in a trigger must hold (AND) for the question to be open.",
179
+ "oneOf": [
180
+ {
181
+ "type": "object",
182
+ "additionalProperties": false,
183
+ "required": [
184
+ "condition",
185
+ "predicate"
186
+ ],
187
+ "properties": {
188
+ "condition": {
189
+ "const": "missing-claim"
190
+ },
191
+ "predicate": {
192
+ "$ref": "#/$defs/claimPredicate"
193
+ }
194
+ }
195
+ },
196
+ {
197
+ "type": "object",
198
+ "additionalProperties": false,
199
+ "required": [
200
+ "condition",
201
+ "kinds",
202
+ "direction"
203
+ ],
204
+ "properties": {
205
+ "condition": {
206
+ "const": "missing-relationship"
207
+ },
208
+ "kinds": {
209
+ "type": "array",
210
+ "minItems": 1,
211
+ "uniqueItems": true,
212
+ "items": {
213
+ "$ref": "#/$defs/qualifiedKind"
214
+ }
215
+ },
216
+ "direction": {
217
+ "enum": [
218
+ "incoming",
219
+ "outgoing",
220
+ "any"
221
+ ]
222
+ },
223
+ "kindMatching": {
224
+ "enum": [
225
+ "exact",
226
+ "descendants"
227
+ ]
228
+ }
229
+ }
230
+ },
231
+ {
232
+ "type": "object",
233
+ "additionalProperties": false,
234
+ "required": [
235
+ "condition"
236
+ ],
237
+ "properties": {
238
+ "condition": {
239
+ "const": "isolated"
240
+ }
241
+ }
242
+ },
243
+ {
244
+ "type": "object",
245
+ "additionalProperties": false,
246
+ "required": [
247
+ "condition",
248
+ "kinds"
249
+ ],
250
+ "properties": {
251
+ "condition": {
252
+ "const": "no-subject-of-kind"
253
+ },
254
+ "kinds": {
255
+ "type": "array",
256
+ "minItems": 1,
257
+ "uniqueItems": true,
258
+ "items": {
259
+ "$ref": "#/$defs/qualifiedKind"
260
+ }
261
+ },
262
+ "kindMatching": {
263
+ "enum": [
264
+ "exact",
265
+ "descendants"
266
+ ],
267
+ "default": "descendants"
268
+ }
269
+ }
270
+ },
271
+ {
272
+ "type": "object",
273
+ "additionalProperties": false,
274
+ "required": [
275
+ "condition"
276
+ ],
277
+ "properties": {
278
+ "condition": {
279
+ "const": "no-state-defined"
280
+ }
281
+ }
282
+ },
283
+ {
284
+ "type": "object",
285
+ "additionalProperties": false,
286
+ "required": [
287
+ "condition",
288
+ "kinds",
289
+ "direction",
290
+ "counterpartKinds"
291
+ ],
292
+ "properties": {
293
+ "condition": {
294
+ "const": "missing-linkage"
295
+ },
296
+ "kinds": {
297
+ "type": "array",
298
+ "minItems": 1,
299
+ "uniqueItems": true,
300
+ "items": {
301
+ "$ref": "#/$defs/qualifiedKind"
302
+ }
303
+ },
304
+ "direction": {
305
+ "enum": [
306
+ "outgoing",
307
+ "incoming"
308
+ ]
309
+ },
310
+ "counterpartKinds": {
311
+ "type": "array",
312
+ "minItems": 1,
313
+ "uniqueItems": true,
314
+ "items": {
315
+ "$ref": "#/$defs/qualifiedKind"
316
+ }
317
+ },
318
+ "kindMatching": {
319
+ "enum": [
320
+ "exact",
321
+ "descendants"
322
+ ],
323
+ "default": "descendants"
324
+ }
325
+ }
326
+ },
327
+ {
328
+ "type": "object",
329
+ "additionalProperties": false,
330
+ "required": [
331
+ "condition",
332
+ "kinds",
333
+ "direction",
334
+ "counterpartKinds"
335
+ ],
336
+ "properties": {
337
+ "condition": {
338
+ "const": "has-linkage"
339
+ },
340
+ "kinds": {
341
+ "type": "array",
342
+ "minItems": 1,
343
+ "uniqueItems": true,
344
+ "items": {
345
+ "$ref": "#/$defs/qualifiedKind"
346
+ }
347
+ },
348
+ "direction": {
349
+ "enum": [
350
+ "outgoing",
351
+ "incoming",
352
+ "either"
353
+ ]
354
+ },
355
+ "counterpartKinds": {
356
+ "type": "array",
357
+ "minItems": 1,
358
+ "uniqueItems": true,
359
+ "items": {
360
+ "$ref": "#/$defs/qualifiedKind"
361
+ }
362
+ },
363
+ "kindMatching": {
364
+ "enum": [
365
+ "exact",
366
+ "descendants"
367
+ ],
368
+ "default": "descendants"
369
+ }
370
+ }
371
+ },
372
+ {
373
+ "type": "object",
374
+ "additionalProperties": false,
375
+ "required": [
376
+ "condition",
377
+ "kinds",
378
+ "direction",
379
+ "counterpartKinds"
380
+ ],
381
+ "properties": {
382
+ "condition": {
383
+ "const": "exists-linkage"
384
+ },
385
+ "kinds": {
386
+ "type": "array",
387
+ "minItems": 1,
388
+ "uniqueItems": true,
389
+ "items": {
390
+ "$ref": "#/$defs/qualifiedKind"
391
+ }
392
+ },
393
+ "direction": {
394
+ "enum": [
395
+ "outgoing",
396
+ "incoming",
397
+ "either"
398
+ ]
399
+ },
400
+ "counterpartKinds": {
401
+ "type": "array",
402
+ "minItems": 1,
403
+ "uniqueItems": true,
404
+ "items": {
405
+ "$ref": "#/$defs/qualifiedKind"
406
+ }
407
+ },
408
+ "kindMatching": {
409
+ "enum": [
410
+ "exact",
411
+ "descendants"
412
+ ],
413
+ "default": "descendants"
414
+ }
415
+ }
416
+ },
417
+ {
418
+ "type": "object",
419
+ "additionalProperties": false,
420
+ "required": [
421
+ "condition",
422
+ "kinds"
423
+ ],
424
+ "properties": {
425
+ "condition": {
426
+ "const": "missing-constraint"
427
+ },
428
+ "kinds": {
429
+ "type": "array",
430
+ "minItems": 1,
431
+ "uniqueItems": true,
432
+ "items": {
433
+ "$ref": "#/$defs/qualifiedKind"
434
+ }
435
+ },
436
+ "kindMatching": {
437
+ "enum": [
438
+ "exact",
439
+ "descendants"
440
+ ],
441
+ "default": "descendants"
442
+ }
443
+ }
444
+ },
445
+ {
446
+ "type": "object",
447
+ "additionalProperties": false,
448
+ "description": "The subject is an endpoint of at least one flow relationship that has no yarramate/flow/content claim.",
449
+ "required": [
450
+ "condition"
451
+ ],
452
+ "properties": {
453
+ "condition": {
454
+ "const": "missing-flow-content"
455
+ }
456
+ }
457
+ },
458
+ {
459
+ "type": "object",
460
+ "additionalProperties": false,
461
+ "required": [
462
+ "condition",
463
+ "predicate",
464
+ "direction"
465
+ ],
466
+ "properties": {
467
+ "condition": {
468
+ "const": "missing-reference"
469
+ },
470
+ "predicate": {
471
+ "$ref": "#/$defs/claimPredicate"
472
+ },
473
+ "direction": {
474
+ "enum": [
475
+ "outgoing",
476
+ "incoming"
477
+ ]
478
+ }
479
+ }
480
+ },
481
+ {
482
+ "type": "object",
483
+ "additionalProperties": false,
484
+ "required": [
485
+ "condition",
486
+ "topic"
487
+ ],
488
+ "properties": {
489
+ "condition": {
490
+ "const": "missing-attestation"
491
+ },
492
+ "topic": {
493
+ "type": "string",
494
+ "pattern": "^[a-z][a-z0-9-]*$"
495
+ }
496
+ }
497
+ },
498
+ {
499
+ "type": "object",
500
+ "additionalProperties": false,
501
+ "description": "The subject resembles another subject of the same kind closely enough to be the same thing under two names, and no yarramate/identity/distinct-from claim dismisses the pair. Deterministic and lexical; the algorithm and thresholds are stated in ADR 0077. Questions using it may interpolate {counterparts}.",
502
+ "required": [
503
+ "condition"
504
+ ],
505
+ "properties": {
506
+ "condition": {
507
+ "const": "near-duplicate"
508
+ }
509
+ }
510
+ },
511
+ {
512
+ "type": "object",
513
+ "additionalProperties": false,
514
+ "description": "The subject's kind is never tested by anything the compiler can check: it participates in no relationship whose kind pins the aspect at the subject's end, so reclassifying it to any other kind of any other aspect would still compile. The kind is a label rather than a constraint (ADR 0083).",
515
+ "required": [
516
+ "condition"
517
+ ],
518
+ "properties": {
519
+ "condition": {
520
+ "const": "unconstrained-kind"
521
+ }
522
+ }
523
+ },
524
+ {
525
+ "type": "object",
526
+ "additionalProperties": false,
527
+ "description": "The subject supersedes a predecessor that is still current, and does not say in what respect. A succession that replaced its predecessor outright says so by the predecessor being gone; one where both remain is usually partial, and the respect is the part a reader needs (ADR 0109).",
528
+ "required": [
529
+ "condition"
530
+ ],
531
+ "properties": {
532
+ "condition": {
533
+ "const": "unscoped-succession"
534
+ }
535
+ }
536
+ }
537
+ ]
538
+ },
539
+ "claimPredicate": {
540
+ "type": "string",
541
+ "pattern": "^[a-z][a-z0-9/@.#-]*$"
542
+ },
543
+ "qualifiedKind": {
544
+ "type": "string",
545
+ "pattern": "^[a-z][a-z0-9/-]*@[0-9][0-9A-Za-z.-]*#[A-Za-z][A-Za-z0-9-]*$"
546
+ }
166
547
  }
167
548
  }
@@ -4,7 +4,14 @@
4
4
  "title": "YarraMate interrogation report",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
- "required": ["format", "workspace", "catalogue", "semantics", "summary", "waves"],
7
+ "required": [
8
+ "format",
9
+ "workspace",
10
+ "catalogue",
11
+ "semantics",
12
+ "summary",
13
+ "waves"
14
+ ],
8
15
  "properties": {
9
16
  "format": {
10
17
  "const": "yarramate/interrogation-report/v1"
@@ -25,11 +32,24 @@
25
32
  "summary": {
26
33
  "type": "object",
27
34
  "additionalProperties": false,
28
- "required": ["questions", "openQuestions", "open"],
35
+ "required": [
36
+ "questions",
37
+ "openQuestions",
38
+ "open"
39
+ ],
29
40
  "properties": {
30
- "questions": { "type": "integer", "minimum": 0 },
31
- "openQuestions": { "type": "integer", "minimum": 0 },
32
- "open": { "type": "integer", "minimum": 0 }
41
+ "questions": {
42
+ "type": "integer",
43
+ "minimum": 0
44
+ },
45
+ "openQuestions": {
46
+ "type": "integer",
47
+ "minimum": 0
48
+ },
49
+ "open": {
50
+ "type": "integer",
51
+ "minimum": 0
52
+ }
33
53
  }
34
54
  },
35
55
  "waves": {
@@ -43,13 +63,25 @@
43
63
  "wave": {
44
64
  "type": "object",
45
65
  "additionalProperties": false,
46
- "required": ["id", "name", "questions"],
66
+ "required": [
67
+ "id",
68
+ "name",
69
+ "questions"
70
+ ],
47
71
  "properties": {
48
- "id": { "type": "string", "minLength": 1 },
49
- "name": { "type": "string", "minLength": 1 },
72
+ "id": {
73
+ "type": "string",
74
+ "minLength": 1
75
+ },
76
+ "name": {
77
+ "type": "string",
78
+ "minLength": 1
79
+ },
50
80
  "questions": {
51
81
  "type": "array",
52
- "items": { "$ref": "#/$defs/question" }
82
+ "items": {
83
+ "$ref": "#/$defs/question"
84
+ }
53
85
  }
54
86
  }
55
87
  },
@@ -63,33 +95,453 @@
63
95
  "open",
64
96
  "question",
65
97
  "materiality",
66
- "resolution"
98
+ "resolution",
99
+ "trigger"
67
100
  ],
68
101
  "properties": {
69
- "id": { "type": "string", "minLength": 1 },
70
- "scope": { "enum": ["workspace", "subject"] },
71
- "authority": { "enum": ["human", "agent", "either"] },
72
- "open": { "type": "boolean" },
73
- "question": { "type": "string", "minLength": 1 },
74
- "materiality": { "type": "string", "minLength": 1 },
75
- "resolution": { "type": "string", "minLength": 1 },
76
- "since": { "type": "string" },
102
+ "id": {
103
+ "type": "string",
104
+ "minLength": 1
105
+ },
106
+ "scope": {
107
+ "enum": [
108
+ "workspace",
109
+ "subject"
110
+ ]
111
+ },
112
+ "authority": {
113
+ "enum": [
114
+ "human",
115
+ "agent",
116
+ "either"
117
+ ]
118
+ },
119
+ "open": {
120
+ "type": "boolean"
121
+ },
122
+ "question": {
123
+ "type": "string",
124
+ "minLength": 1
125
+ },
126
+ "materiality": {
127
+ "type": "string",
128
+ "minLength": 1
129
+ },
130
+ "resolution": {
131
+ "type": "string",
132
+ "minLength": 1
133
+ },
134
+ "trigger": {
135
+ "description": "The catalogue trigger, verbatim: the conditions that opened this question, i.e. its machine-readable answer shape.",
136
+ "type": "array",
137
+ "minItems": 1,
138
+ "items": {
139
+ "$ref": "#/$defs/condition"
140
+ }
141
+ },
142
+ "since": {
143
+ "type": "string"
144
+ },
77
145
  "subjects": {
78
146
  "type": "array",
79
147
  "minItems": 1,
80
- "items": { "$ref": "#/$defs/openSubject" }
148
+ "items": {
149
+ "$ref": "#/$defs/openSubject"
150
+ }
81
151
  }
82
152
  }
83
153
  },
84
154
  "openSubject": {
85
155
  "type": "object",
86
156
  "additionalProperties": false,
87
- "required": ["id", "question"],
157
+ "required": [
158
+ "id",
159
+ "question"
160
+ ],
88
161
  "properties": {
89
- "id": { "type": "string", "minLength": 1 },
90
- "name": { "type": "string", "minLength": 1 },
91
- "question": { "type": "string", "minLength": 1 }
162
+ "id": {
163
+ "type": "string",
164
+ "minLength": 1
165
+ },
166
+ "name": {
167
+ "type": "string",
168
+ "minLength": 1
169
+ },
170
+ "question": {
171
+ "type": "string",
172
+ "minLength": 1
173
+ }
92
174
  }
175
+ },
176
+ "condition": {
177
+ "description": "One deterministic trigger condition. All conditions in a trigger must hold (AND) for the question to be open.",
178
+ "oneOf": [
179
+ {
180
+ "type": "object",
181
+ "additionalProperties": false,
182
+ "required": [
183
+ "condition",
184
+ "predicate"
185
+ ],
186
+ "properties": {
187
+ "condition": {
188
+ "const": "missing-claim"
189
+ },
190
+ "predicate": {
191
+ "$ref": "#/$defs/claimPredicate"
192
+ }
193
+ }
194
+ },
195
+ {
196
+ "type": "object",
197
+ "additionalProperties": false,
198
+ "required": [
199
+ "condition",
200
+ "kinds",
201
+ "direction"
202
+ ],
203
+ "properties": {
204
+ "condition": {
205
+ "const": "missing-relationship"
206
+ },
207
+ "kinds": {
208
+ "type": "array",
209
+ "minItems": 1,
210
+ "uniqueItems": true,
211
+ "items": {
212
+ "$ref": "#/$defs/qualifiedKind"
213
+ }
214
+ },
215
+ "direction": {
216
+ "enum": [
217
+ "incoming",
218
+ "outgoing",
219
+ "any"
220
+ ]
221
+ },
222
+ "kindMatching": {
223
+ "enum": [
224
+ "exact",
225
+ "descendants"
226
+ ]
227
+ }
228
+ }
229
+ },
230
+ {
231
+ "type": "object",
232
+ "additionalProperties": false,
233
+ "required": [
234
+ "condition"
235
+ ],
236
+ "properties": {
237
+ "condition": {
238
+ "const": "isolated"
239
+ }
240
+ }
241
+ },
242
+ {
243
+ "type": "object",
244
+ "additionalProperties": false,
245
+ "required": [
246
+ "condition",
247
+ "kinds"
248
+ ],
249
+ "properties": {
250
+ "condition": {
251
+ "const": "no-subject-of-kind"
252
+ },
253
+ "kinds": {
254
+ "type": "array",
255
+ "minItems": 1,
256
+ "uniqueItems": true,
257
+ "items": {
258
+ "$ref": "#/$defs/qualifiedKind"
259
+ }
260
+ },
261
+ "kindMatching": {
262
+ "enum": [
263
+ "exact",
264
+ "descendants"
265
+ ],
266
+ "default": "descendants"
267
+ }
268
+ }
269
+ },
270
+ {
271
+ "type": "object",
272
+ "additionalProperties": false,
273
+ "required": [
274
+ "condition"
275
+ ],
276
+ "properties": {
277
+ "condition": {
278
+ "const": "no-state-defined"
279
+ }
280
+ }
281
+ },
282
+ {
283
+ "type": "object",
284
+ "additionalProperties": false,
285
+ "required": [
286
+ "condition",
287
+ "kinds",
288
+ "direction",
289
+ "counterpartKinds"
290
+ ],
291
+ "properties": {
292
+ "condition": {
293
+ "const": "missing-linkage"
294
+ },
295
+ "kinds": {
296
+ "type": "array",
297
+ "minItems": 1,
298
+ "uniqueItems": true,
299
+ "items": {
300
+ "$ref": "#/$defs/qualifiedKind"
301
+ }
302
+ },
303
+ "direction": {
304
+ "enum": [
305
+ "outgoing",
306
+ "incoming"
307
+ ]
308
+ },
309
+ "counterpartKinds": {
310
+ "type": "array",
311
+ "minItems": 1,
312
+ "uniqueItems": true,
313
+ "items": {
314
+ "$ref": "#/$defs/qualifiedKind"
315
+ }
316
+ },
317
+ "kindMatching": {
318
+ "enum": [
319
+ "exact",
320
+ "descendants"
321
+ ],
322
+ "default": "descendants"
323
+ }
324
+ }
325
+ },
326
+ {
327
+ "type": "object",
328
+ "additionalProperties": false,
329
+ "required": [
330
+ "condition",
331
+ "kinds",
332
+ "direction",
333
+ "counterpartKinds"
334
+ ],
335
+ "properties": {
336
+ "condition": {
337
+ "const": "has-linkage"
338
+ },
339
+ "kinds": {
340
+ "type": "array",
341
+ "minItems": 1,
342
+ "uniqueItems": true,
343
+ "items": {
344
+ "$ref": "#/$defs/qualifiedKind"
345
+ }
346
+ },
347
+ "direction": {
348
+ "enum": [
349
+ "outgoing",
350
+ "incoming",
351
+ "either"
352
+ ]
353
+ },
354
+ "counterpartKinds": {
355
+ "type": "array",
356
+ "minItems": 1,
357
+ "uniqueItems": true,
358
+ "items": {
359
+ "$ref": "#/$defs/qualifiedKind"
360
+ }
361
+ },
362
+ "kindMatching": {
363
+ "enum": [
364
+ "exact",
365
+ "descendants"
366
+ ],
367
+ "default": "descendants"
368
+ }
369
+ }
370
+ },
371
+ {
372
+ "type": "object",
373
+ "additionalProperties": false,
374
+ "required": [
375
+ "condition",
376
+ "kinds",
377
+ "direction",
378
+ "counterpartKinds"
379
+ ],
380
+ "properties": {
381
+ "condition": {
382
+ "const": "exists-linkage"
383
+ },
384
+ "kinds": {
385
+ "type": "array",
386
+ "minItems": 1,
387
+ "uniqueItems": true,
388
+ "items": {
389
+ "$ref": "#/$defs/qualifiedKind"
390
+ }
391
+ },
392
+ "direction": {
393
+ "enum": [
394
+ "outgoing",
395
+ "incoming",
396
+ "either"
397
+ ]
398
+ },
399
+ "counterpartKinds": {
400
+ "type": "array",
401
+ "minItems": 1,
402
+ "uniqueItems": true,
403
+ "items": {
404
+ "$ref": "#/$defs/qualifiedKind"
405
+ }
406
+ },
407
+ "kindMatching": {
408
+ "enum": [
409
+ "exact",
410
+ "descendants"
411
+ ],
412
+ "default": "descendants"
413
+ }
414
+ }
415
+ },
416
+ {
417
+ "type": "object",
418
+ "additionalProperties": false,
419
+ "required": [
420
+ "condition",
421
+ "kinds"
422
+ ],
423
+ "properties": {
424
+ "condition": {
425
+ "const": "missing-constraint"
426
+ },
427
+ "kinds": {
428
+ "type": "array",
429
+ "minItems": 1,
430
+ "uniqueItems": true,
431
+ "items": {
432
+ "$ref": "#/$defs/qualifiedKind"
433
+ }
434
+ },
435
+ "kindMatching": {
436
+ "enum": [
437
+ "exact",
438
+ "descendants"
439
+ ],
440
+ "default": "descendants"
441
+ }
442
+ }
443
+ },
444
+ {
445
+ "type": "object",
446
+ "additionalProperties": false,
447
+ "description": "The subject is an endpoint of at least one flow relationship that has no yarramate/flow/content claim.",
448
+ "required": [
449
+ "condition"
450
+ ],
451
+ "properties": {
452
+ "condition": {
453
+ "const": "missing-flow-content"
454
+ }
455
+ }
456
+ },
457
+ {
458
+ "type": "object",
459
+ "additionalProperties": false,
460
+ "required": [
461
+ "condition",
462
+ "predicate",
463
+ "direction"
464
+ ],
465
+ "properties": {
466
+ "condition": {
467
+ "const": "missing-reference"
468
+ },
469
+ "predicate": {
470
+ "$ref": "#/$defs/claimPredicate"
471
+ },
472
+ "direction": {
473
+ "enum": [
474
+ "outgoing",
475
+ "incoming"
476
+ ]
477
+ }
478
+ }
479
+ },
480
+ {
481
+ "type": "object",
482
+ "additionalProperties": false,
483
+ "required": [
484
+ "condition",
485
+ "topic"
486
+ ],
487
+ "properties": {
488
+ "condition": {
489
+ "const": "missing-attestation"
490
+ },
491
+ "topic": {
492
+ "type": "string",
493
+ "pattern": "^[a-z][a-z0-9-]*$"
494
+ }
495
+ }
496
+ },
497
+ {
498
+ "type": "object",
499
+ "additionalProperties": false,
500
+ "description": "The subject resembles another subject of the same kind closely enough to be the same thing under two names, and no yarramate/identity/distinct-from claim dismisses the pair. Deterministic and lexical; the algorithm and thresholds are stated in ADR 0077. Questions using it may interpolate {counterparts}.",
501
+ "required": [
502
+ "condition"
503
+ ],
504
+ "properties": {
505
+ "condition": {
506
+ "const": "near-duplicate"
507
+ }
508
+ }
509
+ },
510
+ {
511
+ "type": "object",
512
+ "additionalProperties": false,
513
+ "description": "The subject's kind is never tested by anything the compiler can check: it participates in no relationship whose kind pins the aspect at the subject's end, so reclassifying it to any other kind of any other aspect would still compile. The kind is a label rather than a constraint (ADR 0083).",
514
+ "required": [
515
+ "condition"
516
+ ],
517
+ "properties": {
518
+ "condition": {
519
+ "const": "unconstrained-kind"
520
+ }
521
+ }
522
+ },
523
+ {
524
+ "type": "object",
525
+ "additionalProperties": false,
526
+ "description": "The subject supersedes a predecessor that is still current, and does not say in what respect. A succession that replaced its predecessor outright says so by the predecessor being gone; one where both remain is usually partial, and the respect is the part a reader needs (ADR 0109).",
527
+ "required": [
528
+ "condition"
529
+ ],
530
+ "properties": {
531
+ "condition": {
532
+ "const": "unscoped-succession"
533
+ }
534
+ }
535
+ }
536
+ ]
537
+ },
538
+ "claimPredicate": {
539
+ "type": "string",
540
+ "pattern": "^[a-z][a-z0-9/@.#-]*$"
541
+ },
542
+ "qualifiedKind": {
543
+ "type": "string",
544
+ "pattern": "^[a-z][a-z0-9/-]*@[0-9][0-9A-Za-z.-]*#[A-Za-z][A-Za-z0-9-]*$"
93
545
  }
94
546
  }
95
547
  }
@@ -137,7 +137,11 @@ yarramate design .yarramate/workspace.yaml
137
137
 
138
138
  Each invocation serves exactly the top open question with its subject
139
139
  slice, materiality, and progress — the catalogue is internal; never pass
140
- or read catalogue files. Answer one question at a time: questions the
140
+ or read catalogue files. The step carries the question's `trigger`
141
+ (its machine-readable answer shape), and the human output includes a
142
+ prefilled operations skeleton when the trigger maps onto one
143
+ operation — start from that skeleton instead of authoring the batch
144
+ from memory. Answer one question at a time: questions the
141
145
  model or evidence can answer, answer from your authority; questions
142
146
  marked `human`, relay verbatim with their materiality. Land each answer
143
147
  as subjects and relationships in one atomic batch (`yarramate apply