yarramate 1.3.0 → 1.4.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.
Files changed (40) hide show
  1. package/catalogues/core-enrichment.yaml +13 -1
  2. package/dist/adapters/visual/request.js +8 -1
  3. package/dist/adapters/visual/session-server.js +7 -0
  4. package/dist/adapters/visual/workspace-model.d.ts +14 -2
  5. package/dist/adapters/visual/workspace-model.js +19 -9
  6. package/dist/apply-command.js +7 -0
  7. package/dist/ask-command.js +18 -3
  8. package/dist/check-command.js +10 -1
  9. package/dist/cli-support.d.ts +2 -0
  10. package/dist/cli-support.js +13 -0
  11. package/dist/cli.js +1 -0
  12. package/dist/compiler.d.ts +13 -1
  13. package/dist/compiler.js +646 -0
  14. package/dist/design-command.js +32 -6
  15. package/dist/export-command.js +5 -1
  16. package/dist/graph-projection.d.ts +10 -0
  17. package/dist/graph-projection.js +1 -0
  18. package/dist/interrogate-command.d.ts +17 -0
  19. package/dist/interrogate-command.js +72 -40
  20. package/dist/relationship-drafting.js +16 -1
  21. package/dist/visual-app/assets/{index-C3i9SxTe.js → index-yCHtmQUH.js} +1 -1
  22. package/dist/visual-app/index.html +1 -1
  23. package/dist/visual-app-lib/editor.js +22891 -22264
  24. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +14 -2
  25. package/dist/visual-app-lib/types/compiler.d.ts +13 -1
  26. package/dist/visual-app-lib/types/graph-projection.d.ts +10 -0
  27. package/dist/visual-app-lib/types/interrogate-command.d.ts +17 -0
  28. package/dist/visual-app-lib/types/visual-app/local-host.d.ts +42 -0
  29. package/dist/visual-app-lib/types/workspace.d.ts +2 -0
  30. package/dist/workspace.d.ts +2 -0
  31. package/dist/workspace.js +1 -0
  32. package/docs/CONSUMING-YARRAMATE.md +41 -0
  33. package/package.json +2 -1
  34. package/schema/yarramate-design-step.schema.json +13 -0
  35. package/schema/yarramate-document.schema.json +14 -0
  36. package/schema/yarramate-interrogation-report.schema.json +18 -0
  37. package/schema/yarramate-pattern.schema.json +133 -0
  38. package/schema/yarramate-question-catalogue.schema.json +21 -0
  39. package/schema/yarramate-visual-graph.schema.json +9 -0
  40. package/schema/yarramate-workspace.schema.json +4 -0
@@ -1,6 +1,6 @@
1
1
  format: yarramate/question-catalogue/v1
2
2
  id: core-enrichment
3
- version: "1.2"
3
+ version: "1.3"
4
4
  profile: yarramate/core@0.1
5
5
  presentation:
6
6
  title: Core enrichment interview
@@ -25,27 +25,39 @@ waves:
25
25
  Load-bearing hops: the behavior a component is assigned to or the
26
26
  interface it composes, the serving/triggering/flow mechanism,
27
27
  payload, trust, reliability, and capacity. Hygiene waits.
28
+ opensWhen:
29
+ - condition: has-any-subject
28
30
  - id: business
29
31
  name: Business
30
32
  description: Who acts, what is served, and what information matters.
33
+ opensWhen:
34
+ - condition: has-any-subject
31
35
  - id: application
32
36
  name: Application
33
37
  description: >-
34
38
  How declared services are realized, performed, and fed with
35
39
  information.
40
+ opensWhen:
41
+ - condition: has-any-subject
36
42
  - id: technology
37
43
  name: Technology
38
44
  description: >-
39
45
  Where the declared applications actually run and what materializes
40
46
  them.
47
+ opensWhen:
48
+ - condition: has-any-subject
41
49
  - id: implementation
42
50
  name: Implementation
43
51
  description: >-
44
52
  How the planned architecture becomes real: work, deliverables, and
45
53
  the plateaus between here and there.
54
+ opensWhen:
55
+ - condition: has-any-subject
46
56
  - id: hygiene
47
57
  name: Model hygiene
48
58
  description: Cross-cutting completeness that keeps every wave honest.
59
+ opensWhen:
60
+ - condition: has-any-subject
49
61
 
50
62
  questions:
51
63
  # ---- motivation ----------------------------------------------------------
@@ -58,7 +58,14 @@ export const buildVisualSessionRequest = (options) => {
58
58
  const workspace = loaded.workspace;
59
59
  // Same order the session's own recompile uses, so the request's graph and
60
60
  // every graph the session rebuilds after a commit come from one input list.
61
- const sourcePaths = [...workspace.profiles, ...workspace.documents];
61
+ // Patterns are compiler input like profiles (#268). Omitting them compiled a
62
+ // different workspace than the manifest describes, and an instance binding
63
+ // parts then failed YM419 for a pattern the manifest declared.
64
+ const sourcePaths = [
65
+ ...workspace.profiles,
66
+ ...workspace.patterns,
67
+ ...workspace.documents,
68
+ ];
62
69
  const sources = [];
63
70
  for (const path of sourcePaths) {
64
71
  try {
@@ -504,6 +504,13 @@ export const startVisualServer = async (options) => {
504
504
  try {
505
505
  return [
506
506
  ...resolvedWorkspace.profiles,
507
+ // Patterns are compiler input like profiles (#268). Without them a
508
+ // session recompiles a workspace the manifest does not describe: an
509
+ // instance binding parts fails YM419, `compiledWorkspace` becomes
510
+ // undefined, and every view's filter then matches nothing while the
511
+ // rail still shows the initial model - which reads as a filter bug
512
+ // rather than as a compile that failed.
513
+ ...resolvedWorkspace.patterns,
507
514
  ...resolvedWorkspace.documents,
508
515
  ].map((path) => ({
509
516
  path,
@@ -49,13 +49,25 @@ export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQue
49
49
  * Subject ids come from the same compiled graph as `CanvasNode.id`, so the
50
50
  * join in the browser is a plain lookup.
51
51
  */
52
+ export interface DismissedQuestion {
53
+ readonly questionId: string;
54
+ /** Absent dismisses the question wherever it appears. */
55
+ readonly subject?: string;
56
+ }
52
57
  export declare const interrogationOverlayOf: (compiled: {
53
58
  readonly graph: SemanticGraph;
54
59
  readonly profileContext: ResolvedProfileContext;
55
60
  }, catalogue: {
56
61
  readonly path: string;
57
62
  readonly source: string;
58
- }) => VisualInterrogationOverlay | undefined;
63
+ },
64
+ /**
65
+ * What the host has already dealt with (#328). Evaluation is unchanged and
66
+ * the model is untouched: this decides only what the pane draws, because a
67
+ * question set aside in the host's own product should not be asked again by
68
+ * a pane embedded in it.
69
+ */
70
+ dismissed?: readonly DismissedQuestion[]) => VisualInterrogationOverlay | undefined;
59
71
  /**
60
72
  * Rebuilds the shared editor workspace from one successful compile.
61
73
  *
@@ -71,7 +83,7 @@ export declare const renderedWorkspaceOf: (compiled: {
71
83
  }, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary" | "interrogation">, catalogue?: {
72
84
  readonly path: string;
73
85
  readonly source: string;
74
- }) => {
86
+ }, dismissed?: readonly DismissedQuestion[]) => {
75
87
  readonly model: VisualRenderedModel;
76
88
  readonly views: readonly VisualViewSummary[];
77
89
  };
@@ -46,25 +46,32 @@ export const kindOptionsOf = (lineages) => [...lineages.keys()].map((id) => ({
46
46
  * them would read as five, and the reviewer counting boxes would find three.
47
47
  */
48
48
  export const conceptCountOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.filter(({ type }) => type === "concept").length;
49
+ export const interrogationOverlayOf = (compiled, catalogue,
49
50
  /**
50
- * Folds one interrogation report into what the canvas draws (#292).
51
- *
52
- * Undefined never a throw when the catalogue does not load: the overlay
53
- * is a garnish on the model, and a model frame must not be blocked by it.
54
- * Subject ids come from the same compiled graph as `CanvasNode.id`, so the
55
- * join in the browser is a plain lookup.
51
+ * What the host has already dealt with (#328). Evaluation is unchanged and
52
+ * the model is untouched: this decides only what the pane draws, because a
53
+ * question set aside in the host's own product should not be asked again by
54
+ * a pane embedded in it.
56
55
  */
57
- export const interrogationOverlayOf = (compiled, catalogue) => {
56
+ dismissed = []) => {
58
57
  const loaded = loadQuestionCatalogue(catalogue);
59
58
  if (!loaded.ok)
60
59
  return undefined;
61
60
  const report = evaluateCatalogue(loaded.catalogue, compiled.graph, compiled.profileContext);
61
+ const dismissedEverywhere = new Set(dismissed
62
+ .filter(({ subject }) => subject === undefined)
63
+ .map(({ questionId }) => questionId));
64
+ const dismissedForSubject = new Set(dismissed
65
+ .filter(({ subject }) => subject !== undefined)
66
+ .map(({ questionId, subject }) => `${questionId}\u0000${subject}`));
62
67
  const workspace = [];
63
68
  const subjects = {};
64
69
  for (const wave of report.waves) {
65
70
  for (const question of wave.questions) {
66
71
  if (!question.open)
67
72
  continue;
73
+ if (dismissedEverywhere.has(question.id))
74
+ continue;
68
75
  const base = {
69
76
  questionId: question.id,
70
77
  authority: question.authority,
@@ -75,6 +82,9 @@ export const interrogationOverlayOf = (compiled, catalogue) => {
75
82
  continue;
76
83
  }
77
84
  for (const subject of question.subjects) {
85
+ if (dismissedForSubject.has(`${question.id}\u0000${subject.id}`)) {
86
+ continue;
87
+ }
78
88
  (subjects[subject.id] ??= []).push({
79
89
  ...base,
80
90
  question: subject.question,
@@ -98,14 +108,14 @@ export const interrogationOverlayOf = (compiled, catalogue) => {
98
108
  * is the question catalogue's bytes — this module cannot read files, so
99
109
  * whoever can hands them over; omitting it ships a model with no overlay.
100
110
  */
101
- export const renderedWorkspaceOf = (compiled, views, metadata, catalogue) => {
111
+ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismissed) => {
102
112
  const refreshedViews = views.map((view) => ({
103
113
  ...view,
104
114
  subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext),
105
115
  }));
106
116
  const interrogation = catalogue === undefined
107
117
  ? undefined
108
- : interrogationOverlayOf(compiled, catalogue);
118
+ : interrogationOverlayOf(compiled, catalogue, dismissed);
109
119
  return {
110
120
  model: {
111
121
  ...metadata,
@@ -719,6 +719,10 @@ export const applyOperations = (input) => {
719
719
  // single byte is written; any diagnostic rejects the entire batch.
720
720
  const compiled = [
721
721
  ...resolvedWorkspace.profiles,
722
+ // Without patterns this gate compiles a workspace the manifest does not
723
+ // describe, so every commit against a workspace holding pattern instances
724
+ // was refused with YM419 for a pattern that was declared (#268).
725
+ ...resolvedWorkspace.patterns,
722
726
  ...resolvedWorkspace.documents,
723
727
  ].map((path) => ({
724
728
  path,
@@ -832,6 +836,9 @@ export const planOperations = (store, input) => {
832
836
  // workspace that declares a profile died on it. No operation can target a
833
837
  // profile, so they are read to be COMPILED against, never to be written.
834
838
  ...input.workspace.profiles,
839
+ // Patterns are compiled against too, and are no more writable than a
840
+ // profile: no operation can target one.
841
+ ...input.workspace.patterns,
835
842
  ...input.workspace.documents,
836
843
  ...input.workspace.projections,
837
844
  ...input.workspace.evidence,
@@ -432,7 +432,11 @@ export function runAskCommand(options, cwd) {
432
432
  return emit(result, `Workspace ${workspace.id}: check failing\n` +
433
433
  `Diagnostics: ${plural(checkPayload.diagnostics.length, 'error')}; run \`yarramate check ${workspacePath}\` for details\n`, 1);
434
434
  }
435
- const compilation = compileWorkspaceWithProfileContext([...workspace.profiles, ...workspace.documents].map((path) => ({
435
+ const compilation = compileWorkspaceWithProfileContext([
436
+ ...workspace.profiles,
437
+ ...workspace.patterns,
438
+ ...workspace.documents,
439
+ ].map((path) => ({
436
440
  path,
437
441
  source: readFileSync(resolve(cwd, path), 'utf8'),
438
442
  })));
@@ -500,14 +504,25 @@ export function runAskCommand(options, cwd) {
500
504
  if (reconciliation !== undefined) {
501
505
  lines.push(reconciliationLine(reconciliation));
502
506
  }
507
+ // Zero open has two causes and only one is complete (#334): a
508
+ // catalogue whose waves are all gated shut has asked nothing, and
509
+ // reporting that as a finished interview is the same empty-set
510
+ // flattery the wave rail carried.
511
+ const askedAnything = report.waves.some((wave) => wave.questions.length > 0);
503
512
  lines.push(report.summary.open === 0
504
- ? `Design interview complete (catalogue ${report.catalogue}): no open questions.`
513
+ ? askedAnything
514
+ ? `Design interview complete (catalogue ${report.catalogue}): no open questions.`
515
+ : `Design interview not started (catalogue ${report.catalogue}): no wave has opened yet.`
505
516
  : `Design interview: ${plural(report.summary.open, 'open question')} (catalogue ${report.catalogue}) — continue: yarramate design ${workspacePath}`, '', 'Backlog — planned, dependency order:', ...plannedLines(planned), '', `Current: ${plural(current.length, 'subject')} · Retired: ${retired.length} ` +
506
517
  `(roster: yarramate ask ${workspacePath} --subjects)`);
507
518
  return emit(result, `${lines.join('\n')}\n`);
508
519
  }
509
520
  // Every other mode reads the compiled model directly.
510
- const compilation = compileWorkspaceWithProfileContext([...workspace.profiles, ...workspace.documents].map((path) => ({
521
+ const compilation = compileWorkspaceWithProfileContext([
522
+ ...workspace.profiles,
523
+ ...workspace.patterns,
524
+ ...workspace.documents,
525
+ ].map((path) => ({
511
526
  path,
512
527
  source: readFileSync(resolve(cwd, path), 'utf8'),
513
528
  })));
@@ -274,7 +274,11 @@ export function runCheckCommand(options, cwd) {
274
274
  if (ok && result.ok) {
275
275
  const successfulCounts = counted;
276
276
  const documentCount = result.graph.documents.length;
277
- const profileCount = coreSources.length - documentCount;
277
+ const patternCount = resolved.patterns.length;
278
+ // Everything in `coreSources` that is not a document was a profile until
279
+ // patterns joined the source list (#268), and counting them as profiles
280
+ // said "2 profiles" about a workspace with one.
281
+ const profileCount = coreSources.length - documentCount - patternCount;
278
282
  const mappingCount = mappingSources.length;
279
283
  const projectionCount = resolved.projections.length;
280
284
  const evidenceCount = resolved.evidence.length;
@@ -286,6 +290,11 @@ export function runCheckCommand(options, cwd) {
286
290
  `${profileCount} ${profileCount === 1 ? 'profile' : 'profiles'}`,
287
291
  ]
288
292
  : []),
293
+ ...(patternCount > 0
294
+ ? [
295
+ `${patternCount} ${patternCount === 1 ? 'pattern' : 'patterns'}`,
296
+ ]
297
+ : []),
289
298
  ...(mappingCount > 0
290
299
  ? [
291
300
  `${mappingCount} ${mappingCount === 1 ? 'adapter mapping' : 'adapter mappings'}`,
@@ -28,6 +28,8 @@ export declare const resolveCliWorkspaceSources: (paths: readonly string[], cwd:
28
28
  readonly projections: readonly string[];
29
29
  readonly evidence: readonly string[];
30
30
  readonly contracts: readonly string[];
31
+ /** Pattern documents, which ride in `paths` and are not documents. */
32
+ readonly patterns: readonly string[];
31
33
  } | {
32
34
  readonly ok: false;
33
35
  readonly diagnostics: readonly Diagnostic[];
@@ -50,6 +50,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
50
50
  projections: [],
51
51
  evidence: [],
52
52
  contracts: [],
53
+ patterns: [],
53
54
  };
54
55
  }
55
56
  const manifestPath = paths[0];
@@ -60,6 +61,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
60
61
  projections: [],
61
62
  evidence: [],
62
63
  contracts: [],
64
+ patterns: [],
63
65
  };
64
66
  }
65
67
  const source = readFileSync(resolve(cwd, manifestPath), 'utf8');
@@ -70,6 +72,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
70
72
  projections: [],
71
73
  evidence: [],
72
74
  contracts: [],
75
+ patterns: [],
73
76
  };
74
77
  }
75
78
  const loaded = loadWorkspaceManifest({ path: manifestPath, source }, cwd);
@@ -78,6 +81,15 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
78
81
  ok: true,
79
82
  paths: [
80
83
  ...loaded.workspace.profiles,
84
+ // Patterns are compiler input like profiles, and were resolved from
85
+ // the manifest without ever being handed over (#268): a pattern
86
+ // document a workspace declared was silently ignored by every verb,
87
+ // and an instance binding parts then failed YM419 for a pattern that
88
+ // was sitting right there in the manifest. Every test passed because
89
+ // each hands the compiler an explicit source list rather than
90
+ // resolving a workspace - the check that passes was not the check
91
+ // that mattered.
92
+ ...loaded.workspace.patterns,
81
93
  ...loaded.workspace.documents,
82
94
  ...(options.includeAdapterMappings === true
83
95
  ? loaded.workspace.adapterMappings
@@ -86,6 +98,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
86
98
  projections: loaded.workspace.projections,
87
99
  evidence: loaded.workspace.evidence,
88
100
  contracts: loaded.workspace.contracts,
101
+ patterns: loaded.workspace.patterns,
89
102
  }
90
103
  : { ok: false, diagnostics: loaded.diagnostics };
91
104
  };
package/dist/cli.js CHANGED
@@ -37,6 +37,7 @@ const runReconciliation = (options, cwd) => {
37
37
  }
38
38
  const sourcePaths = [
39
39
  ...loadedWorkspace.workspace.profiles,
40
+ ...loadedWorkspace.workspace.patterns,
40
41
  ...loadedWorkspace.workspace.documents,
41
42
  ];
42
43
  const compilation = compileWorkspace(sourcePaths.map((path) => ({
@@ -74,6 +74,18 @@ export interface ResolvedProfileContext {
74
74
  /** Kind identity -> the core kind it resolves to through lineage. */
75
75
  readonly conceptKindCoreAncestors: ReadonlyMap<string, CoreConceptKindId>;
76
76
  readonly relationshipKindCoreAncestors: ReadonlyMap<string, RelationshipKind>;
77
+ /**
78
+ * The core relationship kinds a pattern PORTS for a concept kind (#268
79
+ * phase 3, ADR 0124), keyed by kind identity. Only kinds that have a
80
+ * pattern with ports appear.
81
+ *
82
+ * A macro edge needs both ends to port its kind, so a consumer offering a
83
+ * palette between two instances intersects the two sets. Two raw groupings
84
+ * permit ten of the eleven kinds, which is no narrowing at all; the ports
85
+ * are what restore the guidance the relationship table gives everywhere
86
+ * else.
87
+ */
88
+ readonly patternPortKinds: ReadonlyMap<string, readonly RelationshipKind[]>;
77
89
  /**
78
90
  * The core relationship kinds the ArchiMate table permits between two
79
91
  * concept kind identities, resolved through lineage; undefined when either
@@ -104,7 +116,7 @@ export type ContextualCompilationResult = {
104
116
  */
105
117
  export interface ParsedWorkspaceSource {
106
118
  readonly source: string;
107
- readonly kind: 'profile' | 'document';
119
+ readonly kind: 'profile' | 'document' | 'pattern';
108
120
  readonly value: unknown;
109
121
  readonly schemaDiagnostics: readonly Diagnostic[];
110
122
  /**