yarramate 1.32.0 → 1.33.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 (55) hide show
  1. package/catalogues/core-enrichment.yaml +70 -0
  2. package/dist/adapters/mcp-cli.js +15 -2
  3. package/dist/brief.js +4 -1
  4. package/dist/cli-support.d.ts +1 -1
  5. package/dist/cli-support.js +1 -1
  6. package/dist/compiler.js +70 -50
  7. package/dist/export-command.js +30 -4
  8. package/dist/graph-projection.d.ts +12 -0
  9. package/dist/graph-projection.js +7 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +2 -0
  12. package/dist/projection.d.ts +5 -0
  13. package/dist/projection.js +4 -0
  14. package/dist/relationship-reading.d.ts +12 -1
  15. package/dist/relationship-reading.js +28 -3
  16. package/dist/responsibility-kinds.d.ts +23 -0
  17. package/dist/responsibility-kinds.js +43 -0
  18. package/dist/responsibility.d.ts +99 -0
  19. package/dist/responsibility.js +239 -0
  20. package/dist/schema-validators.generated.js +37 -25
  21. package/dist/shipped-catalogue.generated.d.ts +1 -1
  22. package/dist/shipped-catalogue.generated.js +1 -1
  23. package/dist/shipped-profile.d.ts +21 -0
  24. package/dist/shipped-profile.js +33 -0
  25. package/dist/tools/export.d.ts +11 -0
  26. package/dist/tools/export.js +26 -0
  27. package/dist/tools/table.js +12 -6
  28. package/dist/tools-entry.d.ts +3 -1
  29. package/dist/tools-entry.js +3 -1
  30. package/dist/visual-app/assets/elk-worker.min-B0rP1gRk.js +22 -0
  31. package/dist/visual-app/assets/elk.bundled-NyuRbCog.js +24 -0
  32. package/dist/visual-app/assets/index-Bg4LA2rt.js +373 -0
  33. package/dist/visual-app/index.html +1 -1
  34. package/dist/visual-app-lib/editor.js +30866 -30043
  35. package/dist/visual-app-lib/types/graph-projection.d.ts +12 -0
  36. package/dist/visual-app-lib/types/projection.d.ts +5 -0
  37. package/dist/visual-app-lib/types/relationship-reading.d.ts +12 -1
  38. package/dist/visual-app-lib/types/responsibility-kinds.d.ts +23 -0
  39. package/dist/visual-app-lib/types/shipped-catalogue.generated.d.ts +1 -1
  40. package/dist/visual-app-lib/types/shipped-profile.d.ts +21 -0
  41. package/dist/visual-app-lib/types/visual-app/elk-layout.d.ts +2 -0
  42. package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +10 -1
  43. package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +3 -2
  44. package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +3 -1
  45. package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +5 -0
  46. package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +4 -1
  47. package/docs/CONSUMING-YARRAMATE.md +1 -1
  48. package/package.json +10 -9
  49. package/schema/yarramate-projection.schema.json +4 -0
  50. package/schema/yarramate-responsibility.schema.json +123 -0
  51. package/schema/yarramate-visual-graph.schema.json +8 -0
  52. package/skills/yarramate-architecture/SKILL.md +1 -0
  53. package/dist/visual-app/assets/elk-worker.min-D8OVqK8T.js +0 -22
  54. package/dist/visual-app/assets/elk.bundled-DwTh4wCo.js +0 -24
  55. package/dist/visual-app/assets/index-DJYV9FNm.js +0 -373
@@ -830,6 +830,76 @@ questions:
830
830
  Add an owner reference to an accountable actor (evidence such as
831
831
  CODEOWNERS may propose it; a human confirms accountability).
832
832
 
833
+ - id: responsible-missing
834
+ wave: business
835
+ since: "1.33"
836
+ scope: subject
837
+ subjects:
838
+ kinds:
839
+ - yarramate/core@0.1#businessService
840
+ - yarramate/core@0.1#applicationService
841
+ - yarramate/core@0.1#applicationComponent
842
+ - yarramate/core@0.1#capability
843
+ trigger:
844
+ - condition: missing-linkage
845
+ kinds:
846
+ - yarramate/policy@0.2#responsible
847
+ direction: incoming
848
+ counterpartKinds:
849
+ - yarramate/core@0.1#businessActor
850
+ - yarramate/core@0.1#businessRole
851
+ - yarramate/core@0.1#businessCollaboration
852
+ - yarramate/core@0.1#stakeholder
853
+ question: >-
854
+ Who is responsible for {subject.name}?
855
+ askPlain: >-
856
+ Who actually builds, runs or delivers "{subject.name}" day to day?
857
+ materiality: >-
858
+ Accountability says whose desk a failure lands on; responsibility says
859
+ whose hands are on it. A subject with nobody responsible has nobody to
860
+ hand the work to, and a RACI row with no R is a gap the matrix reports.
861
+ authority: either
862
+ resolution: >-
863
+ Add a `responsible` relationship from the actor, role or collaboration
864
+ that does the work to the subject (a support contract or CODEOWNERS may
865
+ propose it; a person confirms it).
866
+ - id: role-idle
867
+ wave: business
868
+ since: "1.33"
869
+ scope: subject
870
+ subjects:
871
+ kinds:
872
+ - yarramate/core@0.1#businessActor
873
+ - yarramate/core@0.1#businessRole
874
+ - yarramate/core@0.1#businessCollaboration
875
+ trigger:
876
+ - condition: missing-reference
877
+ predicate: yarramate/ownership/owner
878
+ direction: incoming
879
+ - condition: missing-relationship
880
+ kinds:
881
+ - yarramate/policy@0.2#responsible
882
+ - yarramate/policy@0.2#consulted
883
+ - yarramate/policy@0.2#informed
884
+ direction: outgoing
885
+ - condition: missing-relationship
886
+ kinds:
887
+ - yarramate/core@0.1#serving
888
+ direction: incoming
889
+ question: >-
890
+ What does {subject.name} answer for?
891
+ askPlain: >-
892
+ "{subject.name}" is in the model but owns nothing, is responsible for
893
+ nothing, and is neither consulted nor informed about anything. What is
894
+ their part, or should they go?
895
+ materiality: >-
896
+ A person with no letter is either missing the edges a delivery depends
897
+ on or a name that outlived its role. A served actor is a consumer, not
898
+ a responsibility holder, and is not asked.
899
+ authority: human
900
+ resolution: >-
901
+ Give the person a letter (an owner claim on what they answer for, or a
902
+ responsible, consulted or informed relationship) or retire the subject.
833
903
  - id: actor-unassigned
834
904
  wave: business
835
905
  since: "0.1"
@@ -148,8 +148,8 @@ const runExport = (input, tool, root) => {
148
148
  stderr: '',
149
149
  };
150
150
  }
151
- // A text kind under `out`: markdown and graph are one file, rtm and
152
- // briefs a directory, as the CLI lays them out.
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.
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 === 'responsibility') {
176
+ const result = outcome.result;
177
+ mkdirSync(target, { recursive: true });
178
+ writeFileSync(join(target, 'RESPONSIBILITY.md'), outcome.text, 'utf8');
179
+ if (result !== undefined) {
180
+ writeFileSync(join(target, 'responsibility.json'), `${JSON.stringify(result.matrix, null, 2)}\n`, 'utf8');
181
+ }
182
+ return {
183
+ exitCode: 0,
184
+ stdout: `Wrote RESPONSIBILITY.md and responsibility.json to ${out}\n`,
185
+ stderr: '',
186
+ };
187
+ }
175
188
  if (kind === 'briefs') {
176
189
  const result = outcome.result;
177
190
  mkdirSync(target, { recursive: true });
package/dist/brief.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { conceptKinds } from './profile.js';
2
- import { RELATIONSHIP_READING, humanizeKind } from './relationship-reading.js';
2
+ import { EXTENSION_READING, RELATIONSHIP_READING, 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')
@@ -66,6 +66,9 @@ const relationshipPhrase = (coreKind, fallbackKind, mode, content) => {
66
66
  if (coreKind === 'flow') {
67
67
  return content === undefined ? 'flows to' : `sends ${content} to`;
68
68
  }
69
+ const extension = EXTENSION_READING[fallbackKind];
70
+ if (extension !== undefined)
71
+ return extension;
69
72
  const reading = coreKind === undefined ? undefined : RELATIONSHIP_READING[coreKind];
70
73
  return reading ?? humanizeKind(fallbackKind);
71
74
  };
@@ -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 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 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 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 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,
package/dist/compiler.js CHANGED
@@ -3,7 +3,7 @@ import { conceptKinds, relationshipPolicies, } from './profile.js';
3
3
  import { isCoreConceptKindId, matrixEndpointAspects, permittedRelationshipKinds as tablePermittedKinds, } from './relationship-matrix.js';
4
4
  import { closestCandidate, describeSchemaViolation, } from './source-document.js';
5
5
  import { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue } from './graph-claims.js';
6
- import { shippedPolicyIdentity, shippedPolicySource, } from './shipped-profile.js';
6
+ import { shippedProfileOf } from './shipped-profile.js';
7
7
  import { validateDocument, validateProfile, validatePattern } from './schema-validation.js';
8
8
  const coreProfile = 'yarramate/core@0.1';
9
9
  /**
@@ -371,58 +371,78 @@ function compileWorkspaceResolved(parsed) {
371
371
  const identity = `${value.id}@${value.version}`;
372
372
  pendingProfiles.push({ input, value, identity, positionFor });
373
373
  }
374
- const alreadyDeclaresPolicy = pendingProfiles.some(({ identity }) => identity === shippedPolicyIdentity);
375
- if (!alreadyDeclaresPolicy) {
376
- // This probe runs BEFORE the document gate that rejects a source whose
377
- // schema check failed, so it has to hold its own precondition: a source
378
- // that composes to anything but a mapping - an empty file, a comment-only
379
- // one, a bare scalar - selects no profile at all. It used to read
380
- // `.profile` through an `as` cast, which is what hid the null from the
381
- // typechecker, and an empty document crashed the whole compile with a
382
- // `TypeError` instead of the `YM201 must be object` its schema already
383
- // produces. Every other consumer of a parsed entry checks its diagnostics
384
- // first (the profile walk above, the pattern walk below); this one could
385
- // not, because it runs before that gate exists, so it narrows instead.
386
- const selected = documentInputs.some(({ entry }) => {
387
- const value = entry.value;
388
- return (typeof value === 'object' &&
389
- value !== null &&
390
- value.profile ===
391
- shippedPolicyIdentity);
392
- });
393
- const extended = pendingProfiles.some(({ value }) => value.extends === shippedPolicyIdentity);
394
- if (selected || extended) {
395
- const input = {
396
- path: 'yarramate:profile:yarramate/policy@0.1',
397
- source: shippedPolicySource,
398
- };
399
- const { entry, fresh } = parseWorkspaceSource(input);
400
- const value = entry.value;
401
- if (entry.schemaDiagnostics.length > 0) {
402
- profileDiagnostics.push(...entry.schemaDiagnostics);
403
- }
404
- else if (!validateProfile(value)) {
405
- for (const error of validateProfile.errors ?? []) {
406
- profileDiagnostics.push({
407
- severity: 'error',
408
- code: 'YM201',
409
- message: `Profile schema violation: ${describeSchemaViolation(error)}`,
410
- path: input.path,
411
- pointer: error.instancePath || '/',
412
- line: 1,
413
- column: 1,
414
- });
415
- }
416
- }
417
- else {
418
- pendingProfiles.push({
419
- input,
420
- value,
421
- identity: shippedPolicyIdentity,
422
- positionFor: positionReader(input.source, entry.positions, fresh),
374
+ // The shipped optional profiles (ADR 0095; policy@0.2 per ADR 0159). Each
375
+ // is injected when a document selects it or a profile extends it, and a
376
+ // shipped profile's own parent follows it in, so `extends:
377
+ // yarramate/policy@0.2` brings 0.1 along. A workspace file that declares
378
+ // the same identity wins; the shipped copy is not added beside it.
379
+ //
380
+ // This probe runs BEFORE the document gate that rejects a source whose
381
+ // schema check failed, so it has to hold its own precondition: a source
382
+ // that composes to anything but a mapping - an empty file, a comment-only
383
+ // one, a bare scalar - selects no profile at all. It used to read
384
+ // `.profile` through an `as` cast, which is what hid the null from the
385
+ // typechecker, and an empty document crashed the whole compile with a
386
+ // `TypeError` instead of the `YM201 must be object` its schema already
387
+ // produces. Every other consumer of a parsed entry checks its diagnostics
388
+ // first (the profile walk above, the pattern walk below); this one could
389
+ // not, because it runs before that gate exists, so it narrows instead.
390
+ const declaredIdentities = new Set(pendingProfiles.map(({ identity }) => identity));
391
+ const requestedIdentities = new Set();
392
+ for (const { entry } of documentInputs) {
393
+ const value = entry.value;
394
+ if (typeof value !== 'object' || value === null)
395
+ continue;
396
+ const profile = value.profile;
397
+ if (typeof profile === 'string')
398
+ requestedIdentities.add(profile);
399
+ }
400
+ for (const { value } of pendingProfiles)
401
+ requestedIdentities.add(value.extends);
402
+ const wantedShipped = [];
403
+ const want = (identity) => {
404
+ if (declaredIdentities.has(identity) || wantedShipped.includes(identity))
405
+ return;
406
+ const shipped = shippedProfileOf(identity);
407
+ if (shipped === undefined)
408
+ return;
409
+ wantedShipped.push(identity);
410
+ want(shipped.extends);
411
+ };
412
+ for (const identity of requestedIdentities)
413
+ want(identity);
414
+ for (const identity of wantedShipped) {
415
+ const shipped = shippedProfileOf(identity);
416
+ const input = {
417
+ path: `yarramate:profile:${identity}`,
418
+ source: shipped.source,
419
+ };
420
+ const { entry, fresh } = parseWorkspaceSource(input);
421
+ const value = entry.value;
422
+ if (entry.schemaDiagnostics.length > 0) {
423
+ profileDiagnostics.push(...entry.schemaDiagnostics);
424
+ }
425
+ else if (!validateProfile(value)) {
426
+ for (const error of validateProfile.errors ?? []) {
427
+ profileDiagnostics.push({
428
+ severity: 'error',
429
+ code: 'YM201',
430
+ message: `Profile schema violation: ${describeSchemaViolation(error)}`,
431
+ path: input.path,
432
+ pointer: error.instancePath || '/',
433
+ line: 1,
434
+ column: 1,
423
435
  });
424
436
  }
425
437
  }
438
+ else {
439
+ pendingProfiles.push({
440
+ input,
441
+ value,
442
+ identity,
443
+ positionFor: positionReader(input.source, entry.positions, fresh),
444
+ });
445
+ }
426
446
  }
427
447
  let unresolvedProfiles = pendingProfiles.sort((left, right) => left.identity.localeCompare(right.identity) ||
428
448
  left.input.path.localeCompare(right.input.path));
@@ -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, exportRtm, exportWorkbook, } from './tools/export.js';
11
+ import { briefsFromResult, exportBriefs, exportGraph, exportMarkdown, 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
@@ -70,7 +70,15 @@ const parseExportOptions = (options) => {
70
70
  export function runExportCommand(options, cwd) {
71
71
  const [kind, ...rest] = options;
72
72
  if (kind === undefined ||
73
- !['graph', 'markdown', 'briefs', 'rtm', 'likec4', 'xlsx'].includes(kind)) {
73
+ ![
74
+ 'graph',
75
+ 'markdown',
76
+ 'briefs',
77
+ 'rtm',
78
+ 'responsibility',
79
+ 'likec4',
80
+ 'xlsx',
81
+ ].includes(kind)) {
74
82
  return { exitCode: 2, stdout: '', stderr: usage };
75
83
  }
76
84
  const parsed = parseExportOptions(rest);
@@ -121,9 +129,13 @@ export function runExportCommand(options, cwd) {
121
129
  if (parsed.positionals.length !== expectedPositionals ||
122
130
  workspacePath === undefined ||
123
131
  parsed.json ||
124
- (usesChanged && (kind === 'graph' || kind === 'rtm')) ||
132
+ (usesChanged &&
133
+ (kind === 'graph' || kind === 'rtm' || kind === 'responsibility')) ||
125
134
  (parsed.budget !== undefined && kind !== 'briefs') ||
126
- ((kind === 'briefs' || kind === 'rtm' || kind === 'xlsx') &&
135
+ ((kind === 'briefs' ||
136
+ kind === 'rtm' ||
137
+ kind === 'responsibility' ||
138
+ kind === 'xlsx') &&
127
139
  parsed.out === undefined)) {
128
140
  return { exitCode: 2, stdout: '', stderr: usage };
129
141
  }
@@ -171,6 +183,20 @@ export function runExportCommand(options, cwd) {
171
183
  stderr: '',
172
184
  };
173
185
  }
186
+ if (kind === 'responsibility') {
187
+ const exported = exportResponsibility(tool, projectionPath);
188
+ if (!exported.ok)
189
+ return failedTool(exported);
190
+ const { markdown, matrix } = exported.result;
191
+ writeText(join(parsed.out, 'RESPONSIBILITY.md'), markdown);
192
+ writeText(join(parsed.out, 'responsibility.json'), `${JSON.stringify(matrix, null, 2)}\n`);
193
+ const gaps = matrix.summary.noAccountable + matrix.summary.noResponsible;
194
+ return {
195
+ exitCode: 0,
196
+ stdout: `Wrote RESPONSIBILITY.md and responsibility.json (${matrix.summary.rows} row${matrix.summary.rows === 1 ? '' : 's'}, ${gaps} gap${gaps === 1 ? '' : 's'}) to ${parsed.out}\n`,
197
+ stderr: '',
198
+ };
199
+ }
174
200
  if (kind === 'graph') {
175
201
  const exported = exportGraph(tool);
176
202
  if (!exported.ok)
@@ -67,6 +67,17 @@ export interface CanvasEdge {
67
67
  readonly kind: string;
68
68
  readonly kindLabel: string;
69
69
  readonly coreKindLabel: string;
70
+ /**
71
+ * The kind whose reading the edge label speaks (ADR 0159): an extension
72
+ * identity with a reading of its own, else `coreKindLabel`.
73
+ */
74
+ readonly readingKind?: string;
75
+ /**
76
+ * The responsibility letter this edge carries (#557): R, C or I for the
77
+ * shipped responsibility kinds and their subkinds, null for every other
78
+ * kind. The canvas draws these only when the view asks (`showResponsibility`).
79
+ */
80
+ readonly responsibility?: ResponsibilityLetter | null;
70
81
  readonly from: string;
71
82
  readonly to: string;
72
83
  readonly name: string | null;
@@ -84,4 +95,5 @@ export interface CanvasGraph {
84
95
  readonly nodes: readonly CanvasNode[];
85
96
  readonly edges: readonly CanvasEdge[];
86
97
  }
98
+ import { type ResponsibilityLetter } from './responsibility-kinds.js';
87
99
  export declare function projectGraphForCanvas(graph: SemanticGraph, profileContext: ResolvedProfileContext): CanvasGraph;
@@ -1,5 +1,7 @@
1
1
  import { ATTESTATION_PREDICATE_PREFIX, parseAttestationClaimValue, parseConstraintExpectsValue, } from './graph-claims.js';
2
2
  import { kindLabelOf } from './kind-label.js';
3
+ import { readingKindOf } from './relationship-reading.js';
4
+ import { responsibilityLetterOf } from './responsibility-kinds.js';
3
5
  const CONCEPT_KIND_PREDICATE = 'yarramate/concept/kind';
4
6
  const CONCEPT_NAME_PREDICATE = 'yarramate/concept/name';
5
7
  const CONCEPT_DESCRIPTION_PREDICATE = 'yarramate/concept/description';
@@ -170,6 +172,8 @@ const projectRelationship = (relationshipId, allClaims, ownClaims, profileContex
170
172
  throw new Error(`Relationship "${relationshipId}" is missing its defining claim`);
171
173
  }
172
174
  const kind = definingClaim.predicate;
175
+ const lineage = profileContext.relationshipKindLineages.get(kind);
176
+ const coreKindLabel = kindLabelOf(lineage?.[0] ?? kind);
173
177
  const nameClaim = ownClaims.find((claim) => claim.predicate === RELATIONSHIP_NAME_PREDICATE);
174
178
  const descriptionClaim = ownClaims.find((claim) => claim.predicate === RELATIONSHIP_DESCRIPTION_PREDICATE);
175
179
  const modeClaim = ownClaims.find((claim) => claim.predicate === ACCESS_MODE_PREDICATE);
@@ -181,7 +185,9 @@ const projectRelationship = (relationshipId, allClaims, ownClaims, profileContex
181
185
  document: definingClaim.source.path,
182
186
  kind,
183
187
  kindLabel: kindLabelOf(kind),
184
- coreKindLabel: kindLabelOf(profileContext.relationshipKindLineages.get(kind)?.[0] ?? kind),
188
+ coreKindLabel,
189
+ readingKind: readingKindOf(lineage, coreKindLabel),
190
+ responsibility: responsibilityLetterOf(lineage, kind),
185
191
  from: definingClaim.subject,
186
192
  to: claimRef(definingClaim),
187
193
  name: nameClaim === undefined ? null : claimValue(nameClaim),
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ export { constraintExpectsPredicate, reconcileEvidenceReports, type AssertedRela
10
10
  export { deriveAttestationStaleness } from './attestation-staleness.js';
11
11
  export { deriveArtifactCoverage, type ArtifactCoverage, type CoverageScopePattern, } from './artifact-coverage.js';
12
12
  export { buildRtm, renderRtmMarkdown, type RequirementsTraceabilityMatrix, type RtmAttestation, type RtmContextEntry, type RtmDescopedEntry, type RtmEvidenceVerdict, type RtmLineageEntry, type RtmRealizer, type RtmRow, type RtmSource, } from './rtm.js';
13
+ export { buildResponsibilityMatrix, renderResponsibilityMarkdown, type RaciLetter, type ResponsibilityCell, type ResponsibilityCellSource, type ResponsibilityMatrix, type ResponsibilityOptions, type ResponsibilityPerson, type ResponsibilityRow, type ResponsibilitySource, } from './responsibility.js';
14
+ export { PEOPLE_KINDS, RESPONSIBILITY_KINDS, RESPONSIBILITY_PROFILE, responsibilityLetterOf, type ResponsibilityLetter, } from './responsibility-kinds.js';
13
15
  export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, PatternMembership, PatternPortShape, PatternShape, PatternSlotShape, PatternVacancy, PatternWireShape, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
14
16
  export { canonicalProjection, evaluateProjection, explainProjection, instanceClosureOf, loadProjection, renderProjectionMarkdown, type ConceptFacet, type ProjectionExclusion, } from './projection.js';
15
17
  export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, type AdapterMapping, type AdapterMappingLoadResult, type AdapterMappingValidationResult, type AdapterMappingsValidationResult, type AdapterSubjectMapping, } from './adapter-mapping.js';
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ export { constraintExpectsPredicate, reconcileEvidenceReports, } from './reconci
10
10
  export { deriveAttestationStaleness } from './attestation-staleness.js';
11
11
  export { deriveArtifactCoverage, } from './artifact-coverage.js';
12
12
  export { buildRtm, renderRtmMarkdown, } from './rtm.js';
13
+ export { buildResponsibilityMatrix, renderResponsibilityMarkdown, } from './responsibility.js';
14
+ export { PEOPLE_KINDS, RESPONSIBILITY_KINDS, RESPONSIBILITY_PROFILE, responsibilityLetterOf, } from './responsibility-kinds.js';
13
15
  export { canonicalProjection, evaluateProjection, explainProjection, instanceClosureOf, loadProjection, renderProjectionMarkdown, } from './projection.js';
14
16
  export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
15
17
  export { createFileSystemStore, } from './source-store.js';
@@ -84,6 +84,11 @@ export interface ProjectionDefinition {
84
84
  readonly showLifecycle?: boolean;
85
85
  readonly showEvidence?: boolean;
86
86
  readonly showOwnership?: boolean;
87
+ /**
88
+ * Whether responsibility edges draw on the canvas (#557, ADR 0159). Off
89
+ * when absent: the subject's properties read the letters either way.
90
+ */
91
+ readonly showResponsibility?: boolean;
87
92
  /**
88
93
  * Whether an unnamed relationship is labelled with its reading - "serves",
89
94
  * "served by", "realizes" - or left to its line style and arrowhead
@@ -71,6 +71,7 @@ export function canonicalProjection(projection) {
71
71
  ...(presentation.showLifecycle === undefined ? {} : { showLifecycle: presentation.showLifecycle }),
72
72
  ...(presentation.showEvidence === undefined ? {} : { showEvidence: presentation.showEvidence }),
73
73
  ...(presentation.showOwnership === undefined ? {} : { showOwnership: presentation.showOwnership }),
74
+ ...(presentation.showResponsibility === undefined ? {} : { showResponsibility: presentation.showResponsibility }),
74
75
  ...(presentation.showKindLabels === undefined ? {} : { showKindLabels: presentation.showKindLabels }),
75
76
  ...(presentation.notation === undefined ? {} : { notation: presentation.notation }),
76
77
  },
@@ -637,6 +638,9 @@ export function evaluateProjection(graph, projection, profileContext, membership
637
638
  ...(projection.presentation.showOwnership === undefined
638
639
  ? {}
639
640
  : { showOwnership: projection.presentation.showOwnership }),
641
+ ...(projection.presentation.showResponsibility === undefined
642
+ ? {}
643
+ : { showResponsibility: projection.presentation.showResponsibility }),
640
644
  ...(projection.presentation.showKindLabels === undefined
641
645
  ? {}
642
646
  : { showKindLabels: projection.presentation.showKindLabels }),
@@ -13,6 +13,17 @@
13
13
  * reads an access.
14
14
  */
15
15
  export declare const RELATIONSHIP_READING: Readonly<Record<string, string>>;
16
+ /**
17
+ * Readings for the extension kinds the package ships (ADR 0159), keyed by
18
+ * their full identity: a profile's subkind of one of these reads the same,
19
+ * which `readingKindOf` resolves through the lineage.
20
+ */
21
+ export declare const EXTENSION_READING: Readonly<Record<string, string>>;
22
+ /**
23
+ * The kind whose reading an edge speaks: the most specific member of the
24
+ * lineage (ancestor-first) that has a reading of its own, else the core kind.
25
+ */
26
+ export declare const readingKindOf: (lineage: readonly string[] | undefined, coreKind: string) => string;
16
27
  export declare const REVERSED_READING: Readonly<Record<string, string>>;
17
28
  /**
18
29
  * `acme/p@1#applicationComponent` reads "application component" and
@@ -25,4 +36,4 @@ export declare const humanizeKind: (kind: string) => string;
25
36
  * and gets it only where one exists; a kind with no passive reading keeps its
26
37
  * active one, so a caller can pass the layering's answer straight through.
27
38
  */
28
- export declare const relationshipReading: (coreKind: string, reversed?: boolean) => string;
39
+ export declare const relationshipReading: (kind: string, reversed?: boolean) => string;
@@ -25,6 +25,30 @@ export const RELATIONSHIP_READING = {
25
25
  influence: 'influences',
26
26
  association: 'is associated with',
27
27
  };
28
+ /**
29
+ * Readings for the extension kinds the package ships (ADR 0159), keyed by
30
+ * their full identity: a profile's subkind of one of these reads the same,
31
+ * which `readingKindOf` resolves through the lineage.
32
+ */
33
+ export const EXTENSION_READING = {
34
+ 'yarramate/policy@0.2#responsible': 'is responsible for',
35
+ 'yarramate/policy@0.2#consulted': 'is consulted on',
36
+ 'yarramate/policy@0.2#informed': 'is informed of',
37
+ };
38
+ /**
39
+ * The kind whose reading an edge speaks: the most specific member of the
40
+ * lineage (ancestor-first) that has a reading of its own, else the core kind.
41
+ */
42
+ export const readingKindOf = (lineage, coreKind) => {
43
+ if (lineage !== undefined) {
44
+ for (let index = lineage.length - 1; index >= 0; index -= 1) {
45
+ const member = lineage[index];
46
+ if (EXTENSION_READING[member] !== undefined)
47
+ return member;
48
+ }
49
+ }
50
+ return coreKind;
51
+ };
28
52
  export const REVERSED_READING = {
29
53
  serving: 'served by',
30
54
  realization: 'realized by',
@@ -47,6 +71,7 @@ export const humanizeKind = (kind) => {
47
71
  * and gets it only where one exists; a kind with no passive reading keeps its
48
72
  * active one, so a caller can pass the layering's answer straight through.
49
73
  */
50
- export const relationshipReading = (coreKind, reversed = false) => (reversed ? REVERSED_READING[coreKind] : undefined) ??
51
- RELATIONSHIP_READING[coreKind] ??
52
- humanizeKind(coreKind);
74
+ export const relationshipReading = (kind, reversed = false) => (reversed ? REVERSED_READING[kind] : undefined) ??
75
+ EXTENSION_READING[kind] ??
76
+ RELATIONSHIP_READING[kind] ??
77
+ humanizeKind(kind);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The three responsibility relationship kinds and the letters they carry
3
+ * (#557, ADR 0159): `yarramate/policy@0.2#responsible`, `#consulted` and
4
+ * `#informed`, each a subkind of core `association` from a person to the
5
+ * subject they answer for. Accountable is not a kind: it is the existing
6
+ * `yarramate/ownership/owner` claim. Pure, so the canvas projection, the
7
+ * matrix builder and the editor read one table.
8
+ */
9
+ export declare const RESPONSIBILITY_PROFILE = "yarramate/policy@0.2";
10
+ export type ResponsibilityLetter = 'R' | 'C' | 'I';
11
+ export declare const RESPONSIBILITY_KINDS: Readonly<Record<'responsible' | 'consulted' | 'informed', string>>;
12
+ export declare const RESPONSIBILITY_LETTERS: Readonly<Record<string, ResponsibilityLetter>>;
13
+ export declare const OWNER_PREDICATE = "yarramate/ownership/owner";
14
+ /** The core kinds a person subject may be, ancestors of any profile's own. */
15
+ export declare const PEOPLE_KINDS: readonly string[];
16
+ /**
17
+ * The letter a relationship kind carries, read through its lineage
18
+ * (ancestor-first) so a profile's own subkind of `responsible` is still an
19
+ * R; `null` for every other kind.
20
+ */
21
+ export declare const responsibilityLetterOf: (lineage: readonly string[] | undefined, kind: string) => ResponsibilityLetter | null;
22
+ /** Whether a concept kind is a person, read through its lineage. */
23
+ export declare const isPeopleKind: (lineage: readonly string[] | undefined, kind: string) => boolean;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The three responsibility relationship kinds and the letters they carry
3
+ * (#557, ADR 0159): `yarramate/policy@0.2#responsible`, `#consulted` and
4
+ * `#informed`, each a subkind of core `association` from a person to the
5
+ * subject they answer for. Accountable is not a kind: it is the existing
6
+ * `yarramate/ownership/owner` claim. Pure, so the canvas projection, the
7
+ * matrix builder and the editor read one table.
8
+ */
9
+ export const RESPONSIBILITY_PROFILE = 'yarramate/policy@0.2';
10
+ export const RESPONSIBILITY_KINDS = {
11
+ responsible: `${RESPONSIBILITY_PROFILE}#responsible`,
12
+ consulted: `${RESPONSIBILITY_PROFILE}#consulted`,
13
+ informed: `${RESPONSIBILITY_PROFILE}#informed`,
14
+ };
15
+ export const RESPONSIBILITY_LETTERS = {
16
+ [RESPONSIBILITY_KINDS.responsible]: 'R',
17
+ [RESPONSIBILITY_KINDS.consulted]: 'C',
18
+ [RESPONSIBILITY_KINDS.informed]: 'I',
19
+ };
20
+ export const OWNER_PREDICATE = 'yarramate/ownership/owner';
21
+ /** The core kinds a person subject may be, ancestors of any profile's own. */
22
+ export const PEOPLE_KINDS = [
23
+ 'yarramate/core@0.1#businessActor',
24
+ 'yarramate/core@0.1#businessRole',
25
+ 'yarramate/core@0.1#businessCollaboration',
26
+ 'yarramate/core@0.1#stakeholder',
27
+ ];
28
+ /**
29
+ * The letter a relationship kind carries, read through its lineage
30
+ * (ancestor-first) so a profile's own subkind of `responsible` is still an
31
+ * R; `null` for every other kind.
32
+ */
33
+ export const responsibilityLetterOf = (lineage, kind) => {
34
+ for (const member of lineage ?? [kind]) {
35
+ const letter = RESPONSIBILITY_LETTERS[member];
36
+ if (letter !== undefined)
37
+ return letter;
38
+ }
39
+ return RESPONSIBILITY_LETTERS[kind] ?? null;
40
+ };
41
+ /** Whether a concept kind is a person, read through its lineage. */
42
+ export const isPeopleKind = (lineage, kind) => (lineage ?? [kind]).some((member) => PEOPLE_KINDS.includes(member)) ||
43
+ PEOPLE_KINDS.includes(kind);