yarramate 0.7.1 → 0.8.1

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.
@@ -1,15 +1,18 @@
1
1
  format: yarramate/question-catalogue/v1
2
2
  id: core-enrichment
3
- version: "0.3"
3
+ version: "0.5"
4
4
  profile: yarramate/core@0.1
5
5
  presentation:
6
6
  title: Core enrichment interview
7
7
  description: >-
8
- The guided design path: motivation, business, and application waves plus
9
- cross-cutting hygiene (technology and implementation follow in a later
10
- version). Each question states the decision its answer changes; a
11
- question that cannot is deleted, not softened. Adequacy is enforced by
12
- linkage depth and attestation, never by reading words.
8
+ The guided design path: motivation, business, application, technology,
9
+ and implementation waves plus cross-cutting hygiene. Each question
10
+ states the decision its answer changes; a question that cannot is
11
+ deleted, not softened. Adequacy is enforced by linkage depth and
12
+ attestation, never by reading words. Versioning is additive within a
13
+ major version: every question records the catalogue version it arrived
14
+ in (ADR 0063), and a completed interview honestly reopens when the
15
+ path deepens.
13
16
 
14
17
  waves:
15
18
  - id: motivation
@@ -23,6 +26,16 @@ waves:
23
26
  description: >-
24
27
  How declared services are realized, performed, and fed with
25
28
  information.
29
+ - id: technology
30
+ name: Technology
31
+ description: >-
32
+ Where the declared applications actually run and what materializes
33
+ them.
34
+ - id: implementation
35
+ name: Implementation
36
+ description: >-
37
+ How the planned architecture becomes real: work, deliverables, and
38
+ the plateaus between here and there.
26
39
  - id: hygiene
27
40
  name: Model hygiene
28
41
  description: Cross-cutting completeness that keeps every wave honest.
@@ -31,6 +44,7 @@ questions:
31
44
  # ---- motivation ----------------------------------------------------------
32
45
  - id: outcome-missing
33
46
  wave: motivation
47
+ since: "0.1"
34
48
  scope: workspace
35
49
  trigger:
36
50
  - condition: no-subject-of-kind
@@ -50,6 +64,7 @@ questions:
50
64
 
51
65
  - id: stakeholders-missing
52
66
  wave: motivation
67
+ since: "0.1"
53
68
  scope: workspace
54
69
  trigger:
55
70
  - condition: no-subject-of-kind
@@ -68,6 +83,7 @@ questions:
68
83
 
69
84
  - id: constraints-missing
70
85
  wave: motivation
86
+ since: "0.1"
71
87
  scope: workspace
72
88
  trigger:
73
89
  - condition: no-subject-of-kind
@@ -86,6 +102,7 @@ questions:
86
102
 
87
103
  - id: goal-unrealized
88
104
  wave: motivation
105
+ since: "0.1"
89
106
  scope: subject
90
107
  subjects:
91
108
  kinds:
@@ -105,11 +122,14 @@ questions:
105
122
  authority: either
106
123
  resolution: >-
107
124
  Add realization relationships from the fulfilling capability or
108
- service, or record the aspirational status in the goal's description.
125
+ service, record the aspirational status in the goal's description,
126
+ or retire the goal (status: retired) to record that it no longer
127
+ steers design.
109
128
 
110
129
 
111
130
  - id: goal-no-driver
112
131
  wave: motivation
132
+ since: "0.3"
113
133
  scope: subject
114
134
  subjects:
115
135
  kinds:
@@ -137,6 +157,7 @@ questions:
137
157
 
138
158
  - id: driver-influences-nothing
139
159
  wave: motivation
160
+ since: "0.3"
140
161
  scope: subject
141
162
  subjects:
142
163
  kinds:
@@ -158,6 +179,7 @@ questions:
158
179
 
159
180
  - id: stakeholder-unconcerned
160
181
  wave: motivation
182
+ since: "0.3"
161
183
  scope: subject
162
184
  subjects:
163
185
  kinds:
@@ -180,6 +202,7 @@ questions:
180
202
 
181
203
  - id: requirement-unrealized
182
204
  wave: motivation
205
+ since: "0.3"
183
206
  scope: subject
184
207
  subjects:
185
208
  kinds:
@@ -198,11 +221,15 @@ questions:
198
221
  roadmap.
199
222
  authority: either
200
223
  resolution: >-
201
- Add realization from the fulfilling service, component, or behavior,
202
- or record the descoping decision and remove the requirement.
224
+ Add realization from the fulfilling service, component, or
225
+ behavior; to descope instead, retire the requirement (status:
226
+ retired) — retirement preserves the decision on record and closes
227
+ the question (ADR 0064). Delete only when the history itself is
228
+ noise.
203
229
 
204
230
  - id: principle-unapplied
205
231
  wave: motivation
232
+ since: "0.3"
206
233
  scope: subject
207
234
  subjects:
208
235
  kinds:
@@ -225,6 +252,7 @@ questions:
225
252
 
226
253
  - id: assessment-unlinked
227
254
  wave: motivation
255
+ since: "0.3"
228
256
  scope: subject
229
257
  subjects:
230
258
  kinds:
@@ -243,6 +271,7 @@ questions:
243
271
 
244
272
  - id: motivation-unattested
245
273
  wave: motivation
274
+ since: "0.3"
246
275
  scope: subject
247
276
  subjects:
248
277
  kinds:
@@ -268,6 +297,7 @@ questions:
268
297
  # ---- business ------------------------------------------------------------
269
298
  - id: service-consumer-unknown
270
299
  wave: business
300
+ since: "0.1"
271
301
  scope: subject
272
302
  subjects:
273
303
  kinds:
@@ -291,6 +321,7 @@ questions:
291
321
 
292
322
  - id: owner-missing
293
323
  wave: business
324
+ since: "0.1"
294
325
  scope: subject
295
326
  subjects:
296
327
  kinds:
@@ -313,6 +344,7 @@ questions:
313
344
 
314
345
  - id: actor-unassigned
315
346
  wave: business
347
+ since: "0.1"
316
348
  scope: subject
317
349
  subjects:
318
350
  kinds:
@@ -335,6 +367,7 @@ questions:
335
367
 
336
368
  - id: information-unaccessed
337
369
  wave: business
370
+ since: "0.1"
338
371
  scope: subject
339
372
  subjects:
340
373
  kinds:
@@ -358,6 +391,7 @@ questions:
358
391
 
359
392
  - id: no-service-declared
360
393
  wave: business
394
+ since: "0.1"
361
395
  scope: workspace
362
396
  trigger:
363
397
  - condition: no-subject-of-kind
@@ -378,6 +412,7 @@ questions:
378
412
 
379
413
  - id: service-realizes-no-motivation
380
414
  wave: business
415
+ since: "0.3"
381
416
  scope: subject
382
417
  subjects:
383
418
  kinds:
@@ -405,6 +440,7 @@ questions:
405
440
 
406
441
  - id: process-untriggered
407
442
  wave: business
443
+ since: "0.3"
408
444
  scope: subject
409
445
  subjects:
410
446
  kinds:
@@ -427,6 +463,7 @@ questions:
427
463
 
428
464
  - id: business-service-unrealized
429
465
  wave: business
466
+ since: "0.3"
430
467
  scope: subject
431
468
  subjects:
432
469
  kinds:
@@ -460,6 +497,7 @@ questions:
460
497
  # ---- application ---------------------------------------------------------
461
498
  - id: app-service-unrealized
462
499
  wave: application
500
+ since: "0.3"
463
501
  scope: subject
464
502
  subjects:
465
503
  kinds:
@@ -490,6 +528,7 @@ questions:
490
528
 
491
529
  - id: component-realizes-nothing
492
530
  wave: application
531
+ since: "0.3"
493
532
  scope: subject
494
533
  subjects:
495
534
  kinds:
@@ -515,6 +554,7 @@ questions:
515
554
 
516
555
  - id: behavior-unassigned
517
556
  wave: application
557
+ since: "0.3"
518
558
  scope: subject
519
559
  subjects:
520
560
  kinds:
@@ -546,6 +586,7 @@ questions:
546
586
 
547
587
  - id: event-triggers-nothing
548
588
  wave: application
589
+ since: "0.3"
549
590
  scope: subject
550
591
  subjects:
551
592
  kinds:
@@ -568,6 +609,7 @@ questions:
568
609
 
569
610
  - id: information-unowned
570
611
  wave: application
612
+ since: "0.3"
571
613
  scope: subject
572
614
  subjects:
573
615
  kinds:
@@ -589,6 +631,7 @@ questions:
589
631
 
590
632
  - id: planned-design-unattested
591
633
  wave: application
634
+ since: "0.3"
592
635
  scope: subject
593
636
  subjects:
594
637
  kinds:
@@ -612,9 +655,246 @@ questions:
612
655
  topic "design-review" (revoke by deleting it when the design
613
656
  changes).
614
657
 
658
+ # ---- technology ----------------------------------------------------------
659
+ - id: component-unhosted
660
+ wave: technology
661
+ since: "0.4"
662
+ scope: subject
663
+ subjects:
664
+ kinds:
665
+ - yarramate/core@0.1#applicationComponent
666
+ kindMatching: exact
667
+ statuses:
668
+ - planned
669
+ - current
670
+ trigger:
671
+ - condition: missing-linkage
672
+ kinds:
673
+ - yarramate/core@0.1#serving
674
+ - yarramate/core@0.1#assignment
675
+ direction: incoming
676
+ counterpartKinds:
677
+ - yarramate/core@0.1#node
678
+ - yarramate/core@0.1#device
679
+ - yarramate/core@0.1#systemSoftware
680
+ - yarramate/core@0.1#technologyService
681
+ question: >-
682
+ Where does {subject.name} run?
683
+ materiality: >-
684
+ The hosting boundary decides latency, failure domain, scaling
685
+ model, and data residency; a component with no declared runtime is
686
+ deployed by whoever gets there first. Matching is exact by intent:
687
+ profile-derived module kinds inherit their deployable parent's
688
+ hosting rather than answering separately.
689
+ authority: either
690
+ resolution: >-
691
+ Add the node, device, or system software that hosts it with a
692
+ serving or assignment relationship into the component.
693
+
694
+ - id: node-serves-nothing
695
+ wave: technology
696
+ since: "0.4"
697
+ scope: subject
698
+ subjects:
699
+ kinds:
700
+ - yarramate/core@0.1#node
701
+ - yarramate/core@0.1#device
702
+ - yarramate/core@0.1#systemSoftware
703
+ trigger:
704
+ - condition: missing-relationship
705
+ kinds:
706
+ - yarramate/core@0.1#serving
707
+ - yarramate/core@0.1#assignment
708
+ direction: outgoing
709
+ question: >-
710
+ What does {subject.name} host or serve?
711
+ materiality: >-
712
+ Declared infrastructure that serves nothing is either cost without
713
+ purpose or missing the links that justify it; both change the
714
+ deployment budget.
715
+ authority: either
716
+ resolution: >-
717
+ Add serving or assignment relationships to the applications and
718
+ behavior it hosts, or remove it.
719
+
720
+ - id: technology-service-unrealized
721
+ wave: technology
722
+ since: "0.4"
723
+ scope: subject
724
+ subjects:
725
+ kinds:
726
+ - yarramate/core@0.1#technologyService
727
+ statuses:
728
+ - planned
729
+ - current
730
+ trigger:
731
+ - condition: missing-linkage
732
+ kinds:
733
+ - yarramate/core@0.1#realization
734
+ direction: incoming
735
+ counterpartKinds:
736
+ - yarramate/core@0.1#node
737
+ - yarramate/core@0.1#systemSoftware
738
+ - yarramate/core@0.1#technologyFunction
739
+ - yarramate/core@0.1#technologyProcess
740
+ question: >-
741
+ What provides {subject.name}?
742
+ materiality: >-
743
+ A technology service with no realizing node or behavior is a
744
+ dependency assumed rather than provided; outages and upgrades have
745
+ no owner in the model.
746
+ authority: either
747
+ resolution: >-
748
+ Add realization from the node, system software, or technology
749
+ behavior that provides the service.
750
+
751
+ - id: artifact-unassigned
752
+ wave: technology
753
+ since: "0.4"
754
+ scope: subject
755
+ subjects:
756
+ kinds:
757
+ - yarramate/core@0.1#artifact
758
+ kindMatching: exact
759
+ trigger:
760
+ - condition: missing-relationship
761
+ kinds:
762
+ - yarramate/core@0.1#assignment
763
+ - yarramate/core@0.1#realization
764
+ direction: any
765
+ question: >-
766
+ What deploys {subject.name}, and what does it materialize?
767
+ materiality: >-
768
+ An artifact with no deployment target and nothing it realizes is a
769
+ build output the architecture cannot place; release engineering
770
+ starts from exactly these links.
771
+ authority: either
772
+ resolution: >-
773
+ Assign the artifact to the node that deploys it and add realization
774
+ to the component or data it materializes.
775
+
776
+ # ---- implementation ------------------------------------------------------
777
+ - id: workpackage-delivers-nothing
778
+ wave: implementation
779
+ since: "0.4"
780
+ scope: subject
781
+ subjects:
782
+ kinds:
783
+ - yarramate/core@0.1#workPackage
784
+ trigger:
785
+ - condition: missing-relationship
786
+ kinds:
787
+ - yarramate/core@0.1#realization
788
+ - yarramate/core@0.1#aggregation
789
+ direction: outgoing
790
+ question: >-
791
+ What does {subject.name} produce?
792
+ materiality: >-
793
+ Work with no deliverable cannot be accepted or declared done; the
794
+ deliverable link is what makes progress reviewable rather than
795
+ reported.
796
+ authority: either
797
+ resolution: >-
798
+ Add realization from the work package to its deliverables.
799
+
800
+ - id: workpackage-unassigned
801
+ wave: implementation
802
+ since: "0.4"
803
+ scope: subject
804
+ subjects:
805
+ kinds:
806
+ - yarramate/core@0.1#workPackage
807
+ trigger:
808
+ - condition: missing-linkage
809
+ kinds:
810
+ - yarramate/core@0.1#assignment
811
+ direction: incoming
812
+ counterpartKinds:
813
+ - yarramate/core@0.1#businessActor
814
+ - yarramate/core@0.1#businessRole
815
+ question: >-
816
+ Who is committed to {subject.name}?
817
+ materiality: >-
818
+ An unassigned work package is a plan without capacity; commitment
819
+ is the difference between a roadmap and a wish list.
820
+ authority: human
821
+ resolution: >-
822
+ Add assignment from the actor or role that owns the work.
823
+
824
+ - id: deliverable-realizes-nothing
825
+ wave: implementation
826
+ since: "0.4"
827
+ scope: subject
828
+ subjects:
829
+ kinds:
830
+ - yarramate/core@0.1#deliverable
831
+ kindMatching: exact
832
+ trigger:
833
+ - condition: missing-relationship
834
+ kinds:
835
+ - yarramate/core@0.1#realization
836
+ direction: outgoing
837
+ question: >-
838
+ What does {subject.name} make real?
839
+ materiality: >-
840
+ A deliverable that realizes no architecture element is effort
841
+ disconnected from the design; its acceptance criteria cannot be
842
+ stated architecturally.
843
+ authority: either
844
+ resolution: >-
845
+ Add realization from the deliverable to the plateau, requirement,
846
+ or element it brings into being.
847
+
848
+ - id: plateau-aggregates-nothing
849
+ wave: implementation
850
+ since: "0.4"
851
+ scope: subject
852
+ subjects:
853
+ kinds:
854
+ - yarramate/core@0.1#plateau
855
+ trigger:
856
+ - condition: missing-relationship
857
+ kinds:
858
+ - yarramate/core@0.1#aggregation
859
+ - yarramate/core@0.1#composition
860
+ direction: outgoing
861
+ question: >-
862
+ Which architecture does {subject.name} stabilize?
863
+ materiality: >-
864
+ A plateau that aggregates nothing describes no state anyone can
865
+ stand on; migration planning needs to know what is stable when.
866
+ authority: either
867
+ resolution: >-
868
+ Aggregate the elements that are current during this plateau, or use
869
+ architecture states instead and remove it.
870
+
871
+ - id: gap-unaddressed
872
+ wave: implementation
873
+ since: "0.4"
874
+ scope: subject
875
+ subjects:
876
+ kinds:
877
+ - yarramate/core@0.1#gap
878
+ trigger:
879
+ - condition: missing-relationship
880
+ kinds:
881
+ - yarramate/core@0.1#association
882
+ - yarramate/core@0.1#realization
883
+ direction: any
884
+ question: >-
885
+ What closes {subject.name}?
886
+ materiality: >-
887
+ A named gap nothing addresses is a known risk with no plan; either
888
+ work closes it or the acceptance of it should be on record.
889
+ authority: human
890
+ resolution: >-
891
+ Associate the gap with the plateaus it separates and the
892
+ deliverable that closes it, or record the decision to accept it.
893
+
615
894
  # ---- hygiene -------------------------------------------------------------
616
895
  - id: concept-isolated
617
896
  wave: hygiene
897
+ since: "0.1"
618
898
  scope: subject
619
899
  subjects:
620
900
  kinds:
@@ -640,6 +920,7 @@ questions:
640
920
 
641
921
  - id: concept-undescribed
642
922
  wave: hygiene
923
+ since: "0.1"
643
924
  scope: subject
644
925
  subjects:
645
926
  kinds:
@@ -663,6 +944,7 @@ questions:
663
944
 
664
945
  - id: status-missing
665
946
  wave: hygiene
947
+ since: "0.1"
666
948
  scope: subject
667
949
  subjects:
668
950
  kinds:
@@ -685,6 +967,7 @@ questions:
685
967
 
686
968
  - id: states-undefined
687
969
  wave: hygiene
970
+ since: "0.1"
688
971
  scope: workspace
689
972
  trigger:
690
973
  - condition: no-state-defined
@@ -123,7 +123,24 @@ const itemMap = (source, collection, id) => {
123
123
  };
124
124
  // The indent item fields sit at, read off the item's own first field.
125
125
  const fieldIndentOf = (source, map) => indentAt(source, nodeRange(map.items[0].key)[0]);
126
+ // A flow-style item (`- { id: x, ... }`) cannot take block field lines;
127
+ // splicing them after it corrupts the sequence (Codex dogfood finding,
128
+ // 2026-08-01). The whole item is rewritten as a block mapping with the
129
+ // mutation applied — churn confined to the item being edited.
130
+ const rewriteFlowItem = (source, map, mutate) => {
131
+ const mutated = mutate(map.toJSON());
132
+ const [start, valueEnd] = nodeRange(map);
133
+ const fieldIndent = indentAt(source, start);
134
+ const rendered = reindent(stringify(mutated, { lineWidth: 0 }).trimEnd(), fieldIndent);
135
+ return splice(source, start, valueEnd, rendered);
136
+ };
126
137
  const setScalarField = (source, map, key, value) => {
138
+ if (map.flow) {
139
+ return rewriteFlowItem(source, map, (fields) => ({
140
+ ...fields,
141
+ [key]: value,
142
+ }));
143
+ }
127
144
  const indent = fieldIndentOf(source, map);
128
145
  const rendered = reindent(valueText(value), indent + 2);
129
146
  const pair = pairFor(map, key);
@@ -134,6 +151,15 @@ const setScalarField = (source, map, key, value) => {
134
151
  return splice(source, start, valueEnd, rendered);
135
152
  };
136
153
  const appendListField = (source, map, key, additions) => {
154
+ if (map.flow) {
155
+ return rewriteFlowItem(source, map, (fields) => ({
156
+ ...fields,
157
+ [key]: [
158
+ ...(fields[key] ?? []),
159
+ ...additions,
160
+ ],
161
+ }));
162
+ }
137
163
  const indent = fieldIndentOf(source, map);
138
164
  const pair = pairFor(map, key);
139
165
  if (pair === undefined) {
@@ -163,11 +189,19 @@ const appendListField = (source, map, key, additions) => {
163
189
  return splice(source, start, valueEnd, `\n${sequenceEntries(merged, indent + 2)}`);
164
190
  };
165
191
  // Retraction (#115): delete the field's whole entry, from the start of its
166
- // key line through the end of its value's last line.
192
+ // key line through the end of its value's last line. A flow item is
193
+ // rewritten instead — line-based deletion there would take the whole item
194
+ // with it.
167
195
  const removeField = (source, map, key) => {
168
196
  const pair = pairFor(map, key);
169
197
  if (pair === undefined)
170
198
  return undefined;
199
+ if (map.flow) {
200
+ return rewriteFlowItem(source, map, (fields) => {
201
+ const { [key]: _removed, ...rest } = fields;
202
+ return rest;
203
+ });
204
+ }
171
205
  const start = lineStartOf(source, nodeRange(pair.key)[0]);
172
206
  const valueEnd = pair.value === null || pair.value === undefined
173
207
  ? nodeRange(pair.key)[2]
@@ -33,6 +33,7 @@ const selectStep = (report, subjectFilter) => {
33
33
  question: question.question,
34
34
  materiality: question.materiality,
35
35
  resolution: question.resolution,
36
+ ...(question.since === undefined ? {} : { since: question.since }),
36
37
  };
37
38
  }
38
39
  const subjects = subjectFilter === undefined
@@ -49,6 +50,7 @@ const selectStep = (report, subjectFilter) => {
49
50
  question: first.question,
50
51
  materiality: question.materiality,
51
52
  resolution: question.resolution,
53
+ ...(question.since === undefined ? {} : { since: question.since }),
52
54
  subject: {
53
55
  id: first.id,
54
56
  ...(first.name === undefined ? {} : { name: first.name }),
@@ -44,6 +44,7 @@ export interface CatalogueQuestion {
44
44
  readonly materiality: string;
45
45
  readonly resolution: string;
46
46
  readonly authority: 'human' | 'agent' | 'either';
47
+ readonly since?: string;
47
48
  }
48
49
  export interface QuestionCatalogue {
49
50
  readonly format: 'yarramate/question-catalogue/v1';
@@ -74,6 +75,7 @@ interface ReportQuestion {
74
75
  readonly question: string;
75
76
  readonly materiality: string;
76
77
  readonly resolution: string;
78
+ readonly since?: string;
77
79
  readonly subjects?: readonly OpenSubject[];
78
80
  }
79
81
  export interface InterrogationReport {
@@ -12,12 +12,6 @@ const indexGraph = (graph) => {
12
12
  const stateSubjects = new Set(graph.claims
13
13
  .filter(({ predicate }) => predicate === 'yarramate/state/type')
14
14
  .map(({ subject }) => subject));
15
- // Architecture states carry concept subjects in the graph but are not
16
- // enrichment targets; the catalogue interrogates the model, not the
17
- // planning overlay.
18
- const concepts = new Set(graph.subjects
19
- .filter(({ id, type }) => type === 'concept' && !stateSubjects.has(id))
20
- .map(({ id }) => id));
21
15
  const claimsBySubject = new Map();
22
16
  const kindOf = new Map();
23
17
  const nameOf = new Map();
@@ -50,6 +44,16 @@ const indexGraph = (graph) => {
50
44
  }
51
45
  }
52
46
  }
47
+ // Architecture states carry concept subjects in the graph but are not
48
+ // enrichment targets; the catalogue interrogates the model, not the
49
+ // planning overlay. Retired concepts are excluded for the same reason
50
+ // (ADR 0064): retirement is the recorded decision that a subject left
51
+ // the design conversation, so no question stays open against it.
52
+ const concepts = new Set(graph.subjects
53
+ .filter(({ id, type }) => type === 'concept' &&
54
+ !stateSubjects.has(id) &&
55
+ statusOf.get(id) !== 'retired')
56
+ .map(({ id }) => id));
53
57
  return {
54
58
  concepts,
55
59
  claimsBySubject,
@@ -175,6 +179,7 @@ export function evaluateCatalogue(catalogue, graph, profileContext) {
175
179
  question: question.question.trim(),
176
180
  materiality: question.materiality.trim(),
177
181
  resolution: question.resolution.trim(),
182
+ ...(question.since === undefined ? {} : { since: question.since }),
178
183
  };
179
184
  if (question.scope === 'workspace') {
180
185
  const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext));
@@ -253,12 +258,13 @@ export function renderInterrogationReport(report) {
253
258
  lines.push(` closed ${question.id}`);
254
259
  continue;
255
260
  }
261
+ const sinceMarker = question.since === undefined ? '' : ` [since ${question.since}]`;
256
262
  if (question.subjects === undefined) {
257
- lines.push(` OPEN ${question.id} — ${question.question}`);
263
+ lines.push(` OPEN ${question.id}${sinceMarker} — ${question.question}`);
258
264
  lines.push(` why: ${question.materiality}`);
259
265
  continue;
260
266
  }
261
- lines.push(` OPEN ${question.id} (${question.subjects.length} ${question.subjects.length === 1 ? 'subject' : 'subjects'})`);
267
+ lines.push(` OPEN ${question.id}${sinceMarker} (${question.subjects.length} ${question.subjects.length === 1 ? 'subject' : 'subjects'})`);
262
268
  for (const subject of question.subjects) {
263
269
  lines.push(` ask: "${subject.question}" [authority: ${question.authority}]`);
264
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -147,6 +147,9 @@
147
147
  "type": "string",
148
148
  "minLength": 1
149
149
  }
150
+ },
151
+ "since": {
152
+ "type": "string"
150
153
  }
151
154
  }
152
155
  }
@@ -68,6 +68,7 @@
68
68
  "question": { "type": "string", "minLength": 1 },
69
69
  "materiality": { "type": "string", "minLength": 1 },
70
70
  "resolution": { "type": "string", "minLength": 1 },
71
+ "since": { "type": "string" },
71
72
  "subjects": {
72
73
  "type": "array",
73
74
  "minItems": 1,
@@ -392,6 +392,11 @@
392
392
  "resolution": {
393
393
  "description": "Guidance for how an accepted answer should land as native authoring (which claim, relationship, or state to add).",
394
394
  "$ref": "#/$defs/nonEmptyText"
395
+ },
396
+ "since": {
397
+ "description": "The catalogue version this question first appeared in. Lets consumers distinguish 'the catalogue deepened' from 'the model regressed' when a completed interview reopens (ADR 0063).",
398
+ "type": "string",
399
+ "pattern": "^[0-9]+\\.[0-9]+$"
395
400
  }
396
401
  },
397
402
  "allOf": [