yarramate 1.3.0 → 1.4.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 (32) hide show
  1. package/catalogues/core-enrichment.yaml +13 -1
  2. package/dist/adapters/visual/workspace-model.d.ts +14 -2
  3. package/dist/adapters/visual/workspace-model.js +19 -9
  4. package/dist/ask-command.js +8 -1
  5. package/dist/compiler.d.ts +13 -1
  6. package/dist/compiler.js +646 -0
  7. package/dist/design-command.js +14 -3
  8. package/dist/graph-projection.d.ts +10 -0
  9. package/dist/graph-projection.js +1 -0
  10. package/dist/interrogate-command.d.ts +17 -0
  11. package/dist/interrogate-command.js +72 -40
  12. package/dist/relationship-drafting.js +16 -1
  13. package/dist/visual-app/assets/{index-C3i9SxTe.js → index-yCHtmQUH.js} +1 -1
  14. package/dist/visual-app/index.html +1 -1
  15. package/dist/visual-app-lib/editor.js +22875 -22261
  16. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +14 -2
  17. package/dist/visual-app-lib/types/compiler.d.ts +13 -1
  18. package/dist/visual-app-lib/types/graph-projection.d.ts +10 -0
  19. package/dist/visual-app-lib/types/interrogate-command.d.ts +17 -0
  20. package/dist/visual-app-lib/types/visual-app/local-host.d.ts +42 -0
  21. package/dist/visual-app-lib/types/workspace.d.ts +2 -0
  22. package/dist/workspace.d.ts +2 -0
  23. package/dist/workspace.js +1 -0
  24. package/docs/CONSUMING-YARRAMATE.md +41 -0
  25. package/package.json +2 -1
  26. package/schema/yarramate-design-step.schema.json +13 -0
  27. package/schema/yarramate-document.schema.json +14 -0
  28. package/schema/yarramate-interrogation-report.schema.json +18 -0
  29. package/schema/yarramate-pattern.schema.json +133 -0
  30. package/schema/yarramate-question-catalogue.schema.json +21 -0
  31. package/schema/yarramate-visual-graph.schema.json +9 -0
  32. package/schema/yarramate-workspace.schema.json +4 -0
@@ -282,9 +282,20 @@ export function runDesignCommand(options, cwd) {
282
282
  '',
283
283
  ];
284
284
  if (step === null) {
285
- lines.push(subjectFilter === undefined
286
- ? 'Interview complete: no open questions. The model answers everything the catalogue asks.'
287
- : `Interview complete for ${subjectFilter}: no open questions touch it.`);
285
+ // "No open questions" has two causes and only one of them is complete
286
+ // (#334). Every wave gated shut asks NOTHING, so a blank model reaches
287
+ // zero without a single question having been put - and claiming the
288
+ // model answers everything the catalogue asks is then flatly false
289
+ // about a catalogue that asked nothing. Completion inferred from an
290
+ // empty set is the same fault the wave rail and the report renderer
291
+ // each carried; this is the sentence an agent reads to decide it is
292
+ // done, so it is the worst place for it.
293
+ const asked = report.waves.some((wave) => wave.questions.length > 0);
294
+ lines.push(!asked
295
+ ? 'No wave has opened yet: this catalogue asks nothing until the model has substance. Declare a subject and run this again.'
296
+ : subjectFilter === undefined
297
+ ? 'Interview complete: no open questions. The model answers everything the catalogue asks.'
298
+ : `Interview complete for ${subjectFilter}: no open questions touch it.`);
288
299
  }
289
300
  else {
290
301
  // --facilitate prefers the workshop phrasing and falls back to the
@@ -14,6 +14,16 @@ export interface CanvasNode {
14
14
  * profile kind it was authored as.
15
15
  */
16
16
  readonly coreKindLabel: string;
17
+ /**
18
+ * The core relationship kinds this node's PATTERN ports, or `[]` where its
19
+ * kind has no pattern with ports (#268 phase 3, ADR 0124).
20
+ *
21
+ * A macro edge needs both ends to port its kind, so an editor offering a
22
+ * palette between two instances intersects the two lists. Two raw groupings
23
+ * permit ten of the eleven kinds, which is no guidance at all; this is what
24
+ * restores the narrowing the relationship table gives everywhere else.
25
+ */
26
+ readonly portKinds: readonly string[];
17
27
  readonly layer: Layer | null;
18
28
  readonly aspect: Aspect | null;
19
29
  readonly name: string;
@@ -119,6 +119,7 @@ const projectConcept = (subjectId, claims, profileContext) => {
119
119
  kind,
120
120
  kindLabel: kindLabelOf(kind),
121
121
  coreKindLabel: kindLabelOf(profileContext.conceptKindLineages.get(kind)?.[0] ?? kind),
122
+ portKinds: profileContext.patternPortKinds.get(kind) ?? [],
122
123
  layer: profileContext.conceptKindLayers.get(kind) ?? null,
123
124
  aspect: profileContext.conceptKindAspects.get(kind) ?? null,
124
125
  name: claimValue(nameClaim),
@@ -87,6 +87,8 @@ export type CatalogueCondition = {
87
87
  readonly condition: 'unscoped-succession';
88
88
  } | {
89
89
  readonly condition: 'unchallenged-evidence';
90
+ } | {
91
+ readonly condition: 'has-any-subject';
90
92
  };
91
93
  /**
92
94
  * One observation from the workspace's evidence overlay, reduced to what
@@ -131,6 +133,11 @@ export interface QuestionCatalogue {
131
133
  readonly id: string;
132
134
  readonly name: string;
133
135
  readonly description?: string;
136
+ /**
137
+ * Conditions that must all hold before this wave opens (#334, ADR 0125).
138
+ * Absent means always open, which is what every wave did before this.
139
+ */
140
+ readonly opensWhen?: readonly CatalogueCondition[];
134
141
  }[];
135
142
  readonly questions: readonly CatalogueQuestion[];
136
143
  }
@@ -161,6 +168,16 @@ export interface ReportQuestion {
161
168
  export interface ReportWave {
162
169
  readonly id: string;
163
170
  readonly name: string;
171
+ /**
172
+ * Whether the wave's gate is met (#334, ADR 0125). A wave with no
173
+ * `opensWhen` is always open.
174
+ *
175
+ * A wave reported `false` carries NO questions and contributes nothing to
176
+ * the summary. Its questions are premature rather than answered, and a
177
+ * progress rail that counted them as answered would flatter itself exactly
178
+ * where someone is most likely to trust it.
179
+ */
180
+ readonly opened: boolean;
164
181
  readonly questions: readonly ReportQuestion[];
165
182
  }
166
183
  export interface InterrogationSummary {
@@ -238,6 +238,13 @@ const linkageHits = (index, condition, subjectId, profileContext) => {
238
238
  };
239
239
  const conditionHolds = (index, condition, subjectId, profileContext, evidence) => {
240
240
  switch (condition.condition) {
241
+ case 'has-any-subject':
242
+ // The guard a late wave needs to say "only once the model has
243
+ // substance" (#334). An empty model is not an architecture at rest -
244
+ // ADR 0120's reading, which stays true for a model that HAS started -
245
+ // it has not begun, and asking it how the planned architecture becomes
246
+ // real greets someone ahead of question one.
247
+ return index.concepts.size > 0;
241
248
  case 'unchallenged-evidence':
242
249
  // Fires where the overlay records observations and every one is a
243
250
  // frictionless confirmation: no contradicted, unknown, or
@@ -449,56 +456,71 @@ export function evaluateCatalogue(catalogue, graph, profileContext, evidence) {
449
456
  let open = 0;
450
457
  let openQuestions = 0;
451
458
  const applicableQuestions = catalogue.questions.filter((question) => questionIsApplicable(question, graph.profiles));
459
+ const waveOpens = (wave) => wave.opensWhen === undefined ||
460
+ wave.opensWhen.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
452
461
  const waves = catalogue.waves.map((wave) => ({
453
462
  id: wave.id,
454
463
  name: wave.name,
455
- questions: applicableQuestions
456
- .filter((question) => question.wave === wave.id)
457
- .map((question) => {
458
- const base = {
459
- id: question.id,
460
- scope: question.scope,
461
- authority: question.authority,
462
- question: question.question.trim(),
463
- materiality: question.materiality.trim(),
464
- resolution: question.resolution.trim(),
465
- trigger: question.trigger,
466
- ...(question.since === undefined ? {} : { since: question.since }),
467
- };
468
- if (question.scope === 'workspace') {
469
- const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
470
- if (isOpen) {
471
- open += 1;
472
- openQuestions += 1;
464
+ opened: waveOpens(wave),
465
+ // A closed wave asks nothing. Its questions are not evaluated at all,
466
+ // rather than evaluated and reported closed - the latter would say they
467
+ // had been asked and answered - so they reach neither the report nor the
468
+ // summary.
469
+ questions: !waveOpens(wave)
470
+ ? []
471
+ : applicableQuestions
472
+ .filter((question) => question.wave === wave.id)
473
+ .map((question) => {
474
+ const base = {
475
+ id: question.id,
476
+ scope: question.scope,
477
+ authority: question.authority,
478
+ question: question.question.trim(),
479
+ materiality: question.materiality.trim(),
480
+ resolution: question.resolution.trim(),
481
+ trigger: question.trigger,
482
+ ...(question.since === undefined ? {} : { since: question.since }),
483
+ };
484
+ if (question.scope === 'workspace') {
485
+ const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
486
+ if (isOpen) {
487
+ open += 1;
488
+ openQuestions += 1;
489
+ }
490
+ return { ...base, open: isOpen };
473
491
  }
474
- return { ...base, open: isOpen };
475
- }
476
- const matches = selectSubjects(index, question.subjects, profileContext).filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence)));
477
- if (matches.length === 0) {
478
- return { ...base, open: false };
479
- }
480
- open += matches.length;
481
- openQuestions += 1;
482
- return {
483
- ...base,
484
- open: true,
485
- subjects: matches.map((id) => {
486
- const name = index.nameOf.get(id);
487
- return {
488
- id,
489
- ...(name === undefined ? {} : { name }),
490
- question: renderQuestion(question.question, id, name, describeCounterparts(index, question, id)),
491
- };
492
- }),
493
- };
494
- }),
492
+ const matches = selectSubjects(index, question.subjects, profileContext).filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence)));
493
+ if (matches.length === 0) {
494
+ return { ...base, open: false };
495
+ }
496
+ open += matches.length;
497
+ openQuestions += 1;
498
+ return {
499
+ ...base,
500
+ open: true,
501
+ subjects: matches.map((id) => {
502
+ const name = index.nameOf.get(id);
503
+ return {
504
+ id,
505
+ ...(name === undefined ? {} : { name }),
506
+ question: renderQuestion(question.question, id, name, describeCounterparts(index, question, id)),
507
+ };
508
+ }),
509
+ };
510
+ }),
495
511
  }));
496
512
  return {
497
513
  format: 'yarramate/interrogation-report/v1',
498
514
  catalogue: `${catalogue.id}@${catalogue.version}`,
499
515
  semantics: INTERROGATION_SEMANTICS_VERSION,
500
516
  summary: {
501
- questions: applicableQuestions.length,
517
+ // Questions in OPENED waves only (#334, ADR 0125). A closed wave's
518
+ // questions have not been asked, so counting them in the denominator
519
+ // would report them as answered - "3 of 51" reading as forty-eight
520
+ // done when forty-eight were never put. The denominator grows as the
521
+ // model gains substance and waves open, which is the interview
522
+ // revealing itself rather than a rail filling up.
523
+ questions: waves.reduce((total, wave) => total + wave.questions.length, 0),
502
524
  openQuestions,
503
525
  open,
504
526
  },
@@ -538,6 +560,16 @@ export function renderInterrogationReport(report) {
538
560
  ];
539
561
  for (const wave of report.waves) {
540
562
  lines.push('', `== ${wave.name} ==`);
563
+ // A wave that has not opened must not read like one whose questions are
564
+ // all closed (#334). Both carry no OPEN questions, and a bare heading with
565
+ // nothing under it is the more flattering of the two readings: "nothing
566
+ // outstanding here" rather than "nobody has been asked anything here".
567
+ // The same shape - completion inferred from an empty set - was found in a
568
+ // consuming product's wave rail on the same day.
569
+ if (!wave.opened) {
570
+ lines.push(' not yet — this wave has not opened');
571
+ continue;
572
+ }
541
573
  for (const question of wave.questions) {
542
574
  if (!question.open) {
543
575
  lines.push(` closed ${question.id}`);
@@ -33,9 +33,24 @@ export const connectableKinds = (graph, fromId, toId) => {
33
33
  !isCoreConceptKindId(to.coreKindLabel)) {
34
34
  return [];
35
35
  }
36
- return [
36
+ const permitted = [
37
37
  ...permittedRelationshipKinds(from.coreKindLabel, to.coreKindLabel),
38
38
  ].sort();
39
+ // Between two PATTERN INSTANCES the ports are the narrowing (#268 phase 3,
40
+ // ADR 0124). Two raw groupings permit ten of the eleven kinds, which is no
41
+ // guidance at all, and an edge between two instances is a macro edge -
42
+ // which phase 2 expands only where BOTH patterns port its kind, so an
43
+ // offer wider than the intersection would propose edges that expand into
44
+ // nothing. Where either end has no ports there is no macro grain to speak
45
+ // of, and the table's own answer stands.
46
+ if (from.portKinds.length === 0 || to.portKinds.length === 0)
47
+ return permitted;
48
+ const ported = new Set(to.portKinds);
49
+ const narrowed = permitted.filter((kind) => from.portKinds.includes(kind) && ported.has(kind));
50
+ // A pattern whose ports the table forbids between these two would narrow to
51
+ // nothing, and an empty palette makes the edge undrawable rather than
52
+ // guided. The table's answer is the honest fallback.
53
+ return narrowed.length === 0 ? permitted : narrowed;
39
54
  };
40
55
  /**
41
56
  * An id for a new relationship, unique across the graph.