yarramate 1.33.0 → 1.35.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 (52) hide show
  1. package/catalogues/core-enrichment.yaml +126 -0
  2. package/dist/adapters/mcp-cli.js +14 -1
  3. package/dist/adapters/visual/protocol-contract.d.ts +10 -0
  4. package/dist/adapters/visual/session-server.js +8 -6
  5. package/dist/adapters/visual/workspace-model.d.ts +3 -3
  6. package/dist/adapters/visual/workspace-model.js +27 -15
  7. package/dist/brief.js +12 -2
  8. package/dist/cli-support.d.ts +1 -1
  9. package/dist/cli-support.js +1 -1
  10. package/dist/export-command.js +21 -7
  11. package/dist/governance-kinds.d.ts +24 -0
  12. package/dist/governance-kinds.js +40 -0
  13. package/dist/governance.d.ts +79 -0
  14. package/dist/governance.js +173 -0
  15. package/dist/graph-projection.d.ts +6 -0
  16. package/dist/graph-projection.js +18 -3
  17. package/dist/host/local-host.js +2 -2
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/projection.js +16 -3
  21. package/dist/relationship-reading.d.ts +22 -0
  22. package/dist/relationship-reading.js +38 -0
  23. package/dist/shipped-catalogue.generated.d.ts +1 -1
  24. package/dist/shipped-catalogue.generated.js +1 -1
  25. package/dist/shipped-profile.d.ts +6 -3
  26. package/dist/shipped-profile.js +19 -0
  27. package/dist/tools/ask.js +3 -0
  28. package/dist/tools/design.js +2 -0
  29. package/dist/tools/export.d.ts +10 -0
  30. package/dist/tools/export.js +16 -0
  31. package/dist/tools/table.js +8 -5
  32. package/dist/tools-entry.d.ts +3 -1
  33. package/dist/tools-entry.js +3 -1
  34. package/dist/visual-app/assets/{elk.bundled-NyuRbCog.js → elk.bundled-CgX8dFiz.js} +1 -1
  35. package/dist/visual-app/assets/{index-Bg4LA2rt.js → index-C1vwyq4C.js} +2 -2
  36. package/dist/visual-app/index.html +1 -1
  37. package/dist/visual-app-lib/editor.js +24516 -24478
  38. package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +10 -0
  39. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +3 -3
  40. package/dist/visual-app-lib/types/graph-projection.d.ts +6 -0
  41. package/dist/visual-app-lib/types/relationship-reading.d.ts +22 -0
  42. package/dist/visual-app-lib/types/shipped-catalogue.generated.d.ts +1 -1
  43. package/dist/visual-app-lib/types/shipped-profile.d.ts +6 -3
  44. package/dist/visual-app-lib/types/visual-app/elk-layout.d.ts +2 -0
  45. package/dist/visual-app-lib/types/visual-app/session-client.d.ts +7 -1
  46. package/dist/visual-app-lib/types/visual-app/state.d.ts +1 -0
  47. package/docs/CONSUMING-YARRAMATE.md +1 -1
  48. package/package.json +2 -1
  49. package/schema/yarramate-governance.schema.json +91 -0
  50. package/schema/yarramate-visual-event.schema.json +4 -0
  51. package/schema/yarramate-visual-graph.schema.json +4 -0
  52. package/skills/yarramate-architecture/SKILL.md +1 -0
@@ -900,6 +900,132 @@ questions:
900
900
  resolution: >-
901
901
  Give the person a letter (an owner claim on what they answer for, or a
902
902
  responsible, consulted or informed relationship) or retire the subject.
903
+ - id: risk-threatens-nothing
904
+ wave: motivation
905
+ since: "1.34"
906
+ scope: subject
907
+ subjects:
908
+ kinds:
909
+ - yarramate/policy@0.3#risk
910
+ trigger:
911
+ - condition: missing-linkage
912
+ kinds:
913
+ - yarramate/core@0.1#influence
914
+ direction: outgoing
915
+ counterpartKinds:
916
+ - yarramate/core@0.1#goal
917
+ - yarramate/core@0.1#requirement
918
+ - yarramate/core@0.1#constraint
919
+ question: >-
920
+ What does {subject.name} threaten?
921
+ askPlain: >-
922
+ If "{subject.name}" comes true, which goal, requirement or constraint
923
+ takes the hit?
924
+ materiality: >-
925
+ A risk that threatens nothing named cannot be prioritised or retired:
926
+ its severity has nothing to be measured against, and nobody can say
927
+ when it has passed.
928
+ authority: either
929
+ resolution: >-
930
+ Add an `influence` relationship from the risk to the goal, requirement
931
+ or constraint it threatens.
932
+ - id: risk-unmitigated
933
+ wave: motivation
934
+ since: "1.34"
935
+ scope: subject
936
+ subjects:
937
+ kinds:
938
+ - yarramate/policy@0.3#risk
939
+ trigger:
940
+ - condition: missing-linkage
941
+ kinds:
942
+ - yarramate/core@0.1#influence
943
+ - yarramate/core@0.1#association
944
+ direction: incoming
945
+ counterpartKinds:
946
+ - yarramate/core@0.1#workPackage
947
+ - yarramate/core@0.1#deliverable
948
+ - yarramate/core@0.1#constraint
949
+ - yarramate/core@0.1#courseOfAction
950
+ question: >-
951
+ What mitigates {subject.name}?
952
+ askPlain: >-
953
+ "{subject.name}" is on the log. What work, deliverable, rule or decision
954
+ is in place, or planned, to reduce it?
955
+ materiality: >-
956
+ A risk on the log with no mitigation is a bet the engagement is making
957
+ without saying so; the log exists to make that visible.
958
+ authority: either
959
+ resolution: >-
960
+ Add an `influence` relationship from the work package, deliverable,
961
+ constraint or decision that mitigates it to the risk, or retire the risk.
962
+ - id: risk-unowned
963
+ wave: motivation
964
+ since: "1.34"
965
+ scope: subject
966
+ subjects:
967
+ kinds:
968
+ - yarramate/policy@0.3#risk
969
+ trigger:
970
+ - condition: missing-claim
971
+ predicate: yarramate/ownership/owner
972
+ question: >-
973
+ Who owns {subject.name}?
974
+ askPlain: >-
975
+ Whose name is on "{subject.name}": who reviews it and decides when it
976
+ has passed?
977
+ materiality: >-
978
+ The risk owner is who a review date is asked of; without one,
979
+ risk-reviewed has no authority to sign it.
980
+ authority: either
981
+ resolution: >-
982
+ Add an owner reference to the actor or role that carries the risk.
983
+ - id: assumption-unconfirmed
984
+ wave: motivation
985
+ since: "1.34"
986
+ scope: subject
987
+ subjects:
988
+ kinds:
989
+ - yarramate/policy@0.3#assumption
990
+ trigger:
991
+ - condition: missing-attestation
992
+ topic: assumption-confirmed
993
+ question: >-
994
+ Has anyone confirmed {subject.name}?
995
+ askPlain: >-
996
+ "{subject.name}" is being built on. Has the person who can confirm it
997
+ said so, and when?
998
+ materiality: >-
999
+ An unconfirmed assumption is a risk wearing a calmer name; the
1000
+ confirmation, with its date, is what turns it into a fact the delivery
1001
+ can rest on.
1002
+ authority: human
1003
+ resolution: >-
1004
+ Record an `assumption-confirmed` attestation by the role or actor that
1005
+ can confirm it, dated; or retire the assumption.
1006
+ - id: assumption-bears-on-nothing
1007
+ wave: motivation
1008
+ since: "1.34"
1009
+ scope: subject
1010
+ subjects:
1011
+ kinds:
1012
+ - yarramate/policy@0.3#assumption
1013
+ trigger:
1014
+ - condition: missing-relationship
1015
+ kinds:
1016
+ - yarramate/core@0.1#association
1017
+ direction: outgoing
1018
+ question: >-
1019
+ What does {subject.name} bear on?
1020
+ askPlain: >-
1021
+ If "{subject.name}" turned out false, what in this design would change?
1022
+ materiality: >-
1023
+ An assumption that bears on nothing named is either decorative or
1024
+ missing its edges, and the log cannot say which.
1025
+ authority: either
1026
+ resolution: >-
1027
+ Add an `association` relationship from the assumption to the goal,
1028
+ requirement, constraint or subject it bears on, or retire it.
903
1029
  - id: actor-unassigned
904
1030
  wave: business
905
1031
  since: "0.1"
@@ -149,7 +149,7 @@ const runExport = (input, tool, root) => {
149
149
  };
150
150
  }
151
151
  // A text kind under `out`: markdown and graph are one file, rtm,
152
- // responsibility and briefs a directory, as the CLI lays them out.
152
+ // responsibility, governance and briefs a directory, as the CLI lays them out.
153
153
  if (kind === 'markdown' || kind === 'graph') {
154
154
  mkdirSync(dirname(target), { recursive: true });
155
155
  writeFileSync(target, outcome.text, 'utf8');
@@ -172,6 +172,19 @@ const runExport = (input, tool, root) => {
172
172
  stderr: '',
173
173
  };
174
174
  }
175
+ if (kind === 'governance') {
176
+ const result = outcome.result;
177
+ mkdirSync(target, { recursive: true });
178
+ writeFileSync(join(target, 'GOVERNANCE.md'), outcome.text, 'utf8');
179
+ if (result !== undefined) {
180
+ writeFileSync(join(target, 'governance.json'), `${JSON.stringify(result.log, null, 2)}\n`, 'utf8');
181
+ }
182
+ return {
183
+ exitCode: 0,
184
+ stdout: `Wrote GOVERNANCE.md and governance.json to ${out}\n`,
185
+ stderr: '',
186
+ };
187
+ }
175
188
  if (kind === 'responsibility') {
176
189
  const result = outcome.result;
177
190
  mkdirSync(target, { recursive: true });
@@ -255,6 +255,16 @@ export interface VisualFilterQueryPayload {
255
255
  * exactly as it did before.
256
256
  */
257
257
  readonly nesting?: readonly NestingKind[];
258
+ /**
259
+ * Whether the view shows responsibility edges (#563, ADR 0161).
260
+ *
261
+ * Only the `connected` walk reads it: a responsibility edge the canvas hides
262
+ * must not bring a person into the picture, and one the canvas shows must.
263
+ * Optional, so an older browser and every filter that does not send it
264
+ * evaluate as a view with the flag off, which is what the canvas draws by
265
+ * default.
266
+ */
267
+ readonly showResponsibility?: boolean;
258
268
  }
259
269
  export interface VisualFilterResultPayload {
260
270
  readonly query: ProjectionQuery;
@@ -680,7 +680,7 @@ export const startVisualServer = async (options) => {
680
680
  if (!started.ok)
681
681
  standingDiagnostics = started.diagnostics;
682
682
  }
683
- const filterMatchedIds = (query, nesting) => compiledWorkspace === undefined
683
+ const filterMatchedIds = (query, nesting, showResponsibility) => compiledWorkspace === undefined
684
684
  ? []
685
685
  : matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext,
686
686
  // A view's query can name `instances`, and the facet resolves to the
@@ -688,7 +688,9 @@ export const startVisualServer = async (options) => {
688
688
  compiledWorkspace.patternMemberships,
689
689
  // And it resolves the WRONG closure without the nesting the canvas is
690
690
  // drawing with, which is a wrong number rather than a missing one.
691
- nesting);
691
+ nesting,
692
+ // And the walk reads the responsibility flag (#563).
693
+ showResponsibility);
692
694
  /**
693
695
  * Why a query dropped what it dropped, as the editor's "excluded, and why"
694
696
  * list reads it (#248).
@@ -700,9 +702,9 @@ export const startVisualServer = async (options) => {
700
702
  * the reason the editor shows and the set the canvas draws cannot come from
701
703
  * two readings of the same query.
702
704
  */
703
- const filterExclusions = (query, nesting) => compiledWorkspace === undefined
705
+ const filterExclusions = (query, nesting, showResponsibility) => compiledWorkspace === undefined
704
706
  ? []
705
- : exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext, compiledWorkspace.patternMemberships, nesting);
707
+ : exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext, compiledWorkspace.patternMemberships, nesting, showResponsibility);
706
708
  let listening = false;
707
709
  let bootstrapSpent = false;
708
710
  let agentAttached = false;
@@ -1255,8 +1257,8 @@ export const startVisualServer = async (options) => {
1255
1257
  kind: "filter-result",
1256
1258
  result: {
1257
1259
  query: event.payload.query,
1258
- matchedIds: filterMatchedIds(event.payload.query, event.payload.nesting),
1259
- excluded: filterExclusions(event.payload.query, event.payload.nesting),
1260
+ matchedIds: filterMatchedIds(event.payload.query, event.payload.nesting, event.payload.showResponsibility),
1261
+ excluded: filterExclusions(event.payload.query, event.payload.nesting, event.payload.showResponsibility),
1260
1262
  },
1261
1263
  });
1262
1264
  return;
@@ -57,7 +57,7 @@ export declare const patternOptionsOf: (patterns: readonly PatternShape[], linea
57
57
  * subject count. A view over three components with two relationships between
58
58
  * them would read as five, and the reviewer counting boxes would find three.
59
59
  */
60
- export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => number;
60
+ export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[], showResponsibility?: boolean) => number;
61
61
  /**
62
62
  * Folds one interrogation report into what the canvas draws (#292).
63
63
  *
@@ -125,9 +125,9 @@ export declare const renderedWorkspaceOf: (compiled: {
125
125
  readonly views: readonly VisualViewSummary[];
126
126
  };
127
127
  /** Every subject a query draws, concepts and relationships alike. */
128
- export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly string[];
128
+ export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[], showResponsibility?: boolean) => readonly string[];
129
129
  /** Every concept a query dropped, and the facet that dropped it (#248). */
130
- export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly ProjectionExclusion[];
130
+ export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[], showResponsibility?: boolean) => readonly ProjectionExclusion[];
131
131
  /**
132
132
  * One saved view, as the rail reads it. `subjectCount` is the caller's,
133
133
  * because counting needs a compiled graph and a session builds its first list
@@ -104,7 +104,7 @@ const wiringOf = (pattern, slot) => {
104
104
  * subject count. A view over three components with two relationships between
105
105
  * them would read as five, and the reviewer counting boxes would find three.
106
106
  */
107
- export const conceptCountOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.filter(({ type }) => type === "concept").length;
107
+ export const conceptCountOf = (graph, query, profileContext, memberships, nesting, showResponsibility) => evaluateProjection(graph, adHoc(query, nesting, showResponsibility), profileContext, memberships).subjects.filter(({ type }) => type === "concept").length;
108
108
  export const interrogationOverlayOf = (compiled,
109
109
  /**
110
110
  * The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
@@ -209,7 +209,10 @@ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismis
209
209
  // Each view's OWN nesting, because each view's closure is its own. A rail
210
210
  // sitting beside the canvas must not count a different tree than the
211
211
  // canvas draws.
212
- view.presentation?.nesting),
212
+ view.presentation?.nesting,
213
+ // And each view's own responsibility flag, for the same reason: the
214
+ // walk reads it (#563), and the rail must count what the canvas draws.
215
+ view.presentation?.showResponsibility),
213
216
  }));
214
217
  const interrogation = catalogue === undefined
215
218
  ? undefined
@@ -250,24 +253,33 @@ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismis
250
253
  };
251
254
  };
252
255
  /** Every subject a query draws, concepts and relationships alike. */
253
- export const matchedIdsOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.map(({ id }) => id);
256
+ export const matchedIdsOf = (graph, query, profileContext, memberships, nesting, showResponsibility) => evaluateProjection(graph, adHoc(query, nesting, showResponsibility), profileContext, memberships).subjects.map(({ id }) => id);
254
257
  /** Every concept a query dropped, and the facet that dropped it (#248). */
255
- export const exclusionsOf = (graph, query, profileContext, memberships, nesting) => explainProjection(graph, adHoc(query, nesting), profileContext, memberships);
258
+ export const exclusionsOf = (graph, query, profileContext, memberships, nesting, showResponsibility) => explainProjection(graph, adHoc(query, nesting, showResponsibility), profileContext, memberships);
256
259
  /**
257
260
  * A query on its own is not a projection, and every evaluator here wants one.
258
261
  * The id is a placeholder that never reaches a document.
259
262
  */
260
- const adHoc = (query, nesting) => ({
261
- format: "yarramate/projection/v1",
262
- id: "ad-hoc",
263
- version: "0",
264
- query,
265
- // `query.instances` resolves its closure through the view's nesting, so an
266
- // ad-hoc projection that dropped the nesting would answer a DIFFERENT
267
- // question than the canvas is drawing: 2 subjects against 15 on the
268
- // ApertureX reference, with nothing to say it had (#473 phase 2).
269
- ...(nesting === undefined ? {} : { presentation: { nesting } }),
270
- });
263
+ const adHoc = (query, nesting, showResponsibility) => {
264
+ const presentation = {
265
+ // `query.instances` resolves its closure through the view's nesting, so an
266
+ // ad-hoc projection that dropped the nesting would answer a DIFFERENT
267
+ // question than the canvas is drawing: 2 subjects against 15 on the
268
+ // ApertureX reference, with nothing to say it had (#473 phase 2).
269
+ ...(nesting === undefined ? {} : { nesting }),
270
+ // And the `connected` walk reads this flag (#563, ADR 0161): dropped, the
271
+ // evaluation would walk a responsibility edge the canvas hides and stand a
272
+ // person in the picture with no line to anything.
273
+ ...(showResponsibility === undefined ? {} : { showResponsibility }),
274
+ };
275
+ return {
276
+ format: "yarramate/projection/v1",
277
+ id: "ad-hoc",
278
+ version: "0",
279
+ query,
280
+ ...(Object.keys(presentation).length === 0 ? {} : { presentation }),
281
+ };
282
+ };
271
283
  /**
272
284
  * One saved view, as the rail reads it. `subjectCount` is the caller's,
273
285
  * because counting needs a compiled graph and a session builds its first list
package/dist/brief.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { conceptKinds } from './profile.js';
2
- import { EXTENSION_READING, RELATIONSHIP_READING, humanizeKind } from './relationship-reading.js';
2
+ import { EXTENSION_READING, RELATIONSHIP_READING, contextualReading, humanizeKind } from './relationship-reading.js';
3
3
  const coreKindNames = new Map(conceptKinds.map(({ id, name }) => [id, name]));
4
4
  const motivationKindIds = new Set(conceptKinds
5
5
  .filter(({ layer }) => layer === 'motivation')
@@ -110,8 +110,18 @@ workspaceClaims) {
110
110
  if (claim === undefined || !('ref' in claim.object))
111
111
  continue;
112
112
  const description = claimValue(result.claims, subject.id, 'yarramate/relationship/description');
113
+ const coreKind = coreLocalKind(claim.predicate, profileContext?.relationshipKindLineages);
114
+ // A reading the endpoints decide (ADR 0160) speaks before the kind's own.
115
+ const endpointLineage = (id) => {
116
+ const kind = claimValue(result.claims, id, 'yarramate/concept/kind');
117
+ return kind === undefined ? undefined : profileContext?.conceptKindLineages.get(kind);
118
+ };
119
+ const contextual = coreKind === undefined
120
+ ? undefined
121
+ : contextualReading(endpointLineage(claim.subject), endpointLineage(claim.object.ref), coreKind);
113
122
  const entry = {
114
- phrase: relationshipPhrase(coreLocalKind(claim.predicate, profileContext?.relationshipKindLineages), claim.predicate, claimValue(result.claims, subject.id, 'yarramate/access/mode'), claimValue(result.claims, subject.id, 'yarramate/flow/content')),
123
+ phrase: contextual ??
124
+ relationshipPhrase(coreKind, claim.predicate, claimValue(result.claims, subject.id, 'yarramate/access/mode'), claimValue(result.claims, subject.id, 'yarramate/flow/content')),
115
125
  target: claim.object.ref,
116
126
  ...(description === undefined ? {} : { description }),
117
127
  };
@@ -7,7 +7,7 @@ export interface CliResult {
7
7
  export declare const isMainModule: (moduleUrl: string, entrypoint: string | undefined) => boolean;
8
8
  export declare const packageVersion: string;
9
9
  export declare const versionResult: (binary: string) => CliResult;
10
- export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where \"<free text>\" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export responsibility <projection.yaml> <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
10
+ export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where \"<free text>\" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export responsibility <projection.yaml> <workspace.yaml> --out <directory>\n yarramate export governance <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
11
11
  export declare const diagnosticJson: (diagnostics: unknown) => string;
12
12
  export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counted?: {
13
13
  readonly documents: number;
@@ -22,7 +22,7 @@ export const versionResult = (binary) => ({
22
22
  stdout: `${binary} ${packageVersion}\n`,
23
23
  stderr: '',
24
24
  });
25
- export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where "<free text>" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export responsibility <projection.yaml> <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
25
+ export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where "<free text>" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export responsibility <projection.yaml> <workspace.yaml> --out <directory>\n yarramate export governance <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
26
26
  export const diagnosticJson = (diagnostics) => `${JSON.stringify({
27
27
  format: 'yarramate/diagnostic-result/v1',
28
28
  diagnostics,
@@ -8,7 +8,7 @@ import { humanDiagnostics, usage, } from './cli-support.js';
8
8
  import { evaluateProjection, renderProjectionMarkdown } from './projection.js';
9
9
  import { createFileSystemStore } from './source-store.js';
10
10
  import { posixDirectoryOf } from './apply-command.js';
11
- import { briefsFromResult, exportBriefs, exportGraph, exportMarkdown, exportResponsibility, exportRtm, exportWorkbook, } from './tools/export.js';
11
+ import { briefsFromResult, exportBriefs, exportGraph, exportMarkdown, exportGovernance, exportResponsibility, exportRtm, exportWorkbook, } from './tools/export.js';
12
12
  import { compileOf, readSource, } from './tools/workspace.js';
13
13
  import { loadWorkspaceManifest } from './workspace.js';
14
14
  // Every kind is derived in `tools/export.ts` (ADR 0156): this command
@@ -76,6 +76,7 @@ export function runExportCommand(options, cwd) {
76
76
  'briefs',
77
77
  'rtm',
78
78
  'responsibility',
79
+ 'governance',
79
80
  'likec4',
80
81
  'xlsx',
81
82
  ].includes(kind)) {
@@ -121,20 +122,19 @@ export function runExportCommand(options, cwd) {
121
122
  };
122
123
  }
123
124
  const usesChanged = parsed.changed !== undefined;
124
- const expectedPositionals = kind === 'graph' || kind === 'rtm' || usesChanged ? 1 : 2;
125
+ const wholeWorkspace = kind === 'graph' || kind === 'rtm' || kind === 'governance';
126
+ const expectedPositionals = wholeWorkspace || usesChanged ? 1 : 2;
125
127
  const workspacePath = parsed.positionals[expectedPositionals - 1];
126
- const projectionPath = kind === 'graph' || kind === 'rtm' || usesChanged
127
- ? undefined
128
- : parsed.positionals[0];
128
+ const projectionPath = wholeWorkspace || usesChanged ? undefined : parsed.positionals[0];
129
129
  if (parsed.positionals.length !== expectedPositionals ||
130
130
  workspacePath === undefined ||
131
131
  parsed.json ||
132
- (usesChanged &&
133
- (kind === 'graph' || kind === 'rtm' || kind === 'responsibility')) ||
132
+ (usesChanged && (wholeWorkspace || kind === 'responsibility')) ||
134
133
  (parsed.budget !== undefined && kind !== 'briefs') ||
135
134
  ((kind === 'briefs' ||
136
135
  kind === 'rtm' ||
137
136
  kind === 'responsibility' ||
137
+ kind === 'governance' ||
138
138
  kind === 'xlsx') &&
139
139
  parsed.out === undefined)) {
140
140
  return { exitCode: 2, stdout: '', stderr: usage };
@@ -183,6 +183,20 @@ export function runExportCommand(options, cwd) {
183
183
  stderr: '',
184
184
  };
185
185
  }
186
+ if (kind === 'governance') {
187
+ const exported = exportGovernance(tool);
188
+ if (!exported.ok)
189
+ return failedTool(exported);
190
+ const { markdown, log } = exported.result;
191
+ writeText(join(parsed.out, 'GOVERNANCE.md'), markdown);
192
+ writeText(join(parsed.out, 'governance.json'), `${JSON.stringify(log, null, 2)}\n`);
193
+ const gaps = log.summary.unowned + log.summary.unmitigated + log.summary.unconfirmed + log.summary.unreviewed;
194
+ return {
195
+ exitCode: 0,
196
+ stdout: `Wrote GOVERNANCE.md and governance.json (${log.summary.rows} row${log.summary.rows === 1 ? '' : 's'}, ${gaps} gap${gaps === 1 ? '' : 's'}) to ${parsed.out}\n`,
197
+ stderr: '',
198
+ };
199
+ }
186
200
  if (kind === 'responsibility') {
187
201
  const exported = exportResponsibility(tool, projectionPath);
188
202
  if (!exported.ok)
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The governance concept kinds (#560, ADR 0160): `yarramate/policy@0.3#risk`
3
+ * and `#assumption`, each a subkind of core `assessment`. A decision is the
4
+ * adopter's trial kind under `courseOfAction` until the trial reports, so
5
+ * it is not here. Review dates are attestations on the shipped topics
6
+ * below. Pure, so the canvas projection, the log builder and the brief read
7
+ * one table.
8
+ */
9
+ export declare const GOVERNANCE_PROFILE = "yarramate/policy@0.3";
10
+ export type GovernanceType = 'risk' | 'assumption';
11
+ export declare const GOVERNANCE_KINDS: Readonly<Record<GovernanceType, string>>;
12
+ /**
13
+ * The attestation topic that records a review of each type: a risk owner
14
+ * or the client role attests `risk-reviewed` on a risk, and the client
15
+ * confirms an assumption with `assumption-confirmed`. How old is too old is
16
+ * the adopter's threshold, not the engine's.
17
+ */
18
+ export declare const REVIEW_TOPICS: Readonly<Record<GovernanceType, string>>;
19
+ /**
20
+ * Whether a concept kind is a risk or an assumption, read through its
21
+ * lineage (ancestor-first) so a profile's own subkind counts; null for
22
+ * every other kind.
23
+ */
24
+ export declare const governanceTypeOf: (lineage: readonly string[] | undefined, kind: string) => GovernanceType | null;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The governance concept kinds (#560, ADR 0160): `yarramate/policy@0.3#risk`
3
+ * and `#assumption`, each a subkind of core `assessment`. A decision is the
4
+ * adopter's trial kind under `courseOfAction` until the trial reports, so
5
+ * it is not here. Review dates are attestations on the shipped topics
6
+ * below. Pure, so the canvas projection, the log builder and the brief read
7
+ * one table.
8
+ */
9
+ export const GOVERNANCE_PROFILE = 'yarramate/policy@0.3';
10
+ export const GOVERNANCE_KINDS = {
11
+ risk: `${GOVERNANCE_PROFILE}#risk`,
12
+ assumption: `${GOVERNANCE_PROFILE}#assumption`,
13
+ };
14
+ /**
15
+ * The attestation topic that records a review of each type: a risk owner
16
+ * or the client role attests `risk-reviewed` on a risk, and the client
17
+ * confirms an assumption with `assumption-confirmed`. How old is too old is
18
+ * the adopter's threshold, not the engine's.
19
+ */
20
+ export const REVIEW_TOPICS = {
21
+ risk: 'risk-reviewed',
22
+ assumption: 'assumption-confirmed',
23
+ };
24
+ const TYPE_BY_KIND = {
25
+ [GOVERNANCE_KINDS.risk]: 'risk',
26
+ [GOVERNANCE_KINDS.assumption]: 'assumption',
27
+ };
28
+ /**
29
+ * Whether a concept kind is a risk or an assumption, read through its
30
+ * lineage (ancestor-first) so a profile's own subkind counts; null for
31
+ * every other kind.
32
+ */
33
+ export const governanceTypeOf = (lineage, kind) => {
34
+ for (const member of lineage ?? [kind]) {
35
+ const type = TYPE_BY_KIND[member];
36
+ if (type !== undefined)
37
+ return type;
38
+ }
39
+ return TYPE_BY_KIND[kind] ?? null;
40
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The governance log (#560, ADR 0160): the RAID log's risks and
3
+ * assumptions as the model holds them, derived the way the responsibility
4
+ * matrix is derived and never authored. One row per risk or assumption:
5
+ * its owner and status, what it threatens or bears on, what mitigates it,
6
+ * the latest review attestation, what it supersedes, and the groupings it
7
+ * aggregates into (an adopter's severity or likelihood classes, which the
8
+ * engine carries without knowing the scale). Issues and open questions are
9
+ * the adopter's and the interrogation's; the engine never sees an issue.
10
+ *
11
+ * Deterministic: identical inputs give identical bytes, so CI can diff it.
12
+ */
13
+ import type { ResolvedProfileContext, SemanticGraph } from './compiler.js';
14
+ import { type GovernanceType } from './governance-kinds.js';
15
+ export interface GovernanceSource {
16
+ readonly path: string;
17
+ readonly line: number;
18
+ }
19
+ export interface GovernanceRef {
20
+ readonly id: string;
21
+ readonly name: string;
22
+ }
23
+ /** The latest review attestation on a row, by date. */
24
+ export interface GovernanceReview {
25
+ readonly topic: string;
26
+ readonly on: string;
27
+ readonly by: string;
28
+ readonly source: GovernanceSource;
29
+ }
30
+ export interface GovernanceRow {
31
+ readonly subject: string;
32
+ readonly name: string;
33
+ readonly kind: string;
34
+ readonly type: GovernanceType;
35
+ readonly status: string | null;
36
+ readonly description?: string;
37
+ readonly owner: GovernanceRef | null;
38
+ /** A risk's outgoing `influence` targets: what it threatens. */
39
+ readonly threatens: readonly GovernanceRef[];
40
+ /** An assumption's outgoing `association` targets: what it bears on. */
41
+ readonly bearsOn: readonly GovernanceRef[];
42
+ /** Incoming `influence` or `association` sources on a risk: what mitigates it. */
43
+ readonly mitigatedBy: readonly GovernanceRef[];
44
+ readonly review: GovernanceReview | null;
45
+ readonly supersedes: readonly string[];
46
+ /** The groupings that aggregate this row: an adopter's rating classes. */
47
+ readonly groupedBy: readonly GovernanceRef[];
48
+ }
49
+ export interface GovernanceLog {
50
+ readonly format: 'yarramate/governance/v1';
51
+ readonly workspace: string;
52
+ readonly summary: {
53
+ readonly rows: number;
54
+ readonly risks: number;
55
+ readonly assumptions: number;
56
+ readonly unowned: number;
57
+ readonly unmitigated: number;
58
+ readonly unconfirmed: number;
59
+ readonly unreviewed: number;
60
+ };
61
+ readonly rows: readonly GovernanceRow[];
62
+ readonly gaps: {
63
+ /** Rows with no owner. */
64
+ readonly unowned: readonly string[];
65
+ /** Current risks nothing mitigates. */
66
+ readonly unmitigated: readonly string[];
67
+ /** Assumptions with no `assumption-confirmed` attestation. */
68
+ readonly unconfirmed: readonly string[];
69
+ /** Risks with no `risk-reviewed` attestation. How old is too old is the adopter's. */
70
+ readonly unreviewed: readonly string[];
71
+ };
72
+ }
73
+ export interface GovernanceOptions {
74
+ /** The subjects that may get a row; absent, every risk and assumption. */
75
+ readonly rows?: readonly string[];
76
+ }
77
+ export declare function buildGovernanceLog(workspace: string, graph: SemanticGraph, profileContext: ResolvedProfileContext, options?: GovernanceOptions): GovernanceLog;
78
+ /** The log as one markdown table a person reads, with the gaps under it. */
79
+ export declare function renderGovernanceMarkdown(log: GovernanceLog): string;