yarramate 1.32.0 → 1.34.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.
- package/catalogues/core-enrichment.yaml +210 -0
- package/dist/adapters/mcp-cli.js +28 -2
- package/dist/brief.js +15 -2
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/compiler.js +70 -50
- package/dist/export-command.js +48 -8
- package/dist/governance-kinds.d.ts +24 -0
- package/dist/governance-kinds.js +40 -0
- package/dist/governance.d.ts +79 -0
- package/dist/governance.js +164 -0
- package/dist/graph-projection.d.ts +18 -0
- package/dist/graph-projection.js +24 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/projection.d.ts +5 -0
- package/dist/projection.js +4 -0
- package/dist/relationship-reading.d.ts +34 -1
- package/dist/relationship-reading.js +66 -3
- package/dist/responsibility-kinds.d.ts +23 -0
- package/dist/responsibility-kinds.js +43 -0
- package/dist/responsibility.d.ts +99 -0
- package/dist/responsibility.js +239 -0
- package/dist/schema-validators.generated.js +37 -25
- package/dist/shipped-catalogue.generated.d.ts +1 -1
- package/dist/shipped-catalogue.generated.js +1 -1
- package/dist/shipped-profile.d.ts +24 -0
- package/dist/shipped-profile.js +52 -0
- package/dist/tools/export.d.ts +21 -0
- package/dist/tools/export.js +40 -0
- package/dist/tools/table.js +15 -6
- package/dist/tools-entry.d.ts +5 -1
- package/dist/tools-entry.js +5 -1
- package/dist/visual-app/assets/elk-worker.min-B0rP1gRk.js +22 -0
- package/dist/visual-app/assets/elk.bundled-CFiHtVQt.js +24 -0
- package/dist/visual-app/assets/index-BaQ0vy3y.js +373 -0
- package/dist/visual-app/index.html +1 -1
- package/dist/visual-app-lib/editor.js +30875 -30029
- package/dist/visual-app-lib/types/graph-projection.d.ts +18 -0
- package/dist/visual-app-lib/types/projection.d.ts +5 -0
- package/dist/visual-app-lib/types/relationship-reading.d.ts +34 -1
- package/dist/visual-app-lib/types/responsibility-kinds.d.ts +23 -0
- package/dist/visual-app-lib/types/shipped-catalogue.generated.d.ts +1 -1
- package/dist/visual-app-lib/types/shipped-profile.d.ts +24 -0
- package/dist/visual-app-lib/types/visual-app/elk-layout.d.ts +4 -0
- package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +10 -1
- package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +3 -2
- package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +3 -1
- package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +5 -0
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +4 -1
- package/docs/CONSUMING-YARRAMATE.md +1 -1
- package/package.json +11 -9
- package/schema/yarramate-governance.schema.json +91 -0
- package/schema/yarramate-projection.schema.json +4 -0
- package/schema/yarramate-responsibility.schema.json +123 -0
- package/schema/yarramate-visual-graph.schema.json +12 -0
- package/skills/yarramate-architecture/SKILL.md +2 -0
- package/dist/visual-app/assets/elk-worker.min-D8OVqK8T.js +0 -22
- package/dist/visual-app/assets/elk.bundled-DwTh4wCo.js +0 -24
- package/dist/visual-app/assets/index-DJYV9FNm.js +0 -373
|
@@ -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;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { ATTESTATION_PREDICATE_PREFIX, parseAttestationClaimValue, } from './graph-claims.js';
|
|
2
|
+
import { REVIEW_TOPICS, governanceTypeOf, } from './governance-kinds.js';
|
|
3
|
+
const CORE = 'yarramate/core@0.1#';
|
|
4
|
+
const documentOf = (subject) => {
|
|
5
|
+
const hash = subject.indexOf('#');
|
|
6
|
+
return hash === -1 ? '' : subject.slice(0, hash);
|
|
7
|
+
};
|
|
8
|
+
const bySubjectOrder = (left, right) => documentOf(left).localeCompare(documentOf(right)) ||
|
|
9
|
+
left.localeCompare(right);
|
|
10
|
+
const citation = (claim) => ({
|
|
11
|
+
path: claim.source.path,
|
|
12
|
+
line: claim.source.line,
|
|
13
|
+
});
|
|
14
|
+
export function buildGovernanceLog(workspace, graph, profileContext, options = {}) {
|
|
15
|
+
const conceptIds = new Set(graph.subjects.filter(({ type }) => type === 'concept').map(({ id }) => id));
|
|
16
|
+
const relationshipIds = new Set(graph.subjects
|
|
17
|
+
.filter(({ type }) => type === 'relationship')
|
|
18
|
+
.map(({ id }) => id));
|
|
19
|
+
const values = new Map();
|
|
20
|
+
const refs = new Map();
|
|
21
|
+
const attestations = new Map();
|
|
22
|
+
const definingClaims = [];
|
|
23
|
+
for (const claim of graph.claims) {
|
|
24
|
+
if (relationshipIds.has(claim.id) && 'ref' in claim.object) {
|
|
25
|
+
definingClaims.push(claim);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if ('ref' in claim.object) {
|
|
29
|
+
const key = `${claim.subject} ${claim.predicate}`;
|
|
30
|
+
refs.set(key, [...(refs.get(key) ?? []), claim.object.ref]);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (claim.predicate.startsWith(ATTESTATION_PREDICATE_PREFIX)) {
|
|
34
|
+
attestations.set(claim.subject, [
|
|
35
|
+
...(attestations.get(claim.subject) ?? []),
|
|
36
|
+
claim,
|
|
37
|
+
]);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
values.set(`${claim.subject} ${claim.predicate}`, claim.object.value);
|
|
41
|
+
}
|
|
42
|
+
const value = (subject, predicate) => values.get(`${subject} ${predicate}`);
|
|
43
|
+
const refsOf = (subject, predicate) => refs.get(`${subject} ${predicate}`) ?? [];
|
|
44
|
+
const kindOf = (subject) => value(subject, 'yarramate/concept/kind') ?? '';
|
|
45
|
+
const nameOf = (subject) => value(subject, 'yarramate/concept/name') ?? subject;
|
|
46
|
+
const refOf = (id) => ({ id, name: nameOf(id) });
|
|
47
|
+
const typeOf = (subject) => {
|
|
48
|
+
const kind = kindOf(subject);
|
|
49
|
+
return governanceTypeOf(profileContext.conceptKindLineages.get(kind), kind);
|
|
50
|
+
};
|
|
51
|
+
const coreKindOf = (relationshipKind) => (profileContext.relationshipKindLineages.get(relationshipKind) ?? [
|
|
52
|
+
relationshipKind,
|
|
53
|
+
])[0];
|
|
54
|
+
const rowIds = (options.rows ?? [...conceptIds])
|
|
55
|
+
.filter((id) => conceptIds.has(id) && typeOf(id) !== null)
|
|
56
|
+
.sort(bySubjectOrder);
|
|
57
|
+
const rowSet = new Set(rowIds);
|
|
58
|
+
const outgoing = new Map();
|
|
59
|
+
const incoming = new Map();
|
|
60
|
+
for (const claim of definingClaims) {
|
|
61
|
+
if (!('ref' in claim.object))
|
|
62
|
+
continue;
|
|
63
|
+
const core = coreKindOf(claim.predicate);
|
|
64
|
+
if (rowSet.has(claim.subject)) {
|
|
65
|
+
outgoing.set(claim.subject, [
|
|
66
|
+
...(outgoing.get(claim.subject) ?? []),
|
|
67
|
+
{ core, to: claim.object.ref },
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
if (rowSet.has(claim.object.ref)) {
|
|
71
|
+
incoming.set(claim.object.ref, [
|
|
72
|
+
...(incoming.get(claim.object.ref) ?? []),
|
|
73
|
+
{ core, from: claim.subject },
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const targets = (subject, core) => (outgoing.get(subject) ?? [])
|
|
78
|
+
.filter((edge) => edge.core === `${CORE}${core}`)
|
|
79
|
+
.map(({ to }) => to)
|
|
80
|
+
.sort(bySubjectOrder)
|
|
81
|
+
.map(refOf);
|
|
82
|
+
const sources = (subject, cores) => (incoming.get(subject) ?? [])
|
|
83
|
+
.filter((edge) => cores.some((core) => edge.core === `${CORE}${core}`))
|
|
84
|
+
.map(({ from }) => from)
|
|
85
|
+
.sort(bySubjectOrder)
|
|
86
|
+
.map(refOf);
|
|
87
|
+
const latestReview = (subject, topic) => {
|
|
88
|
+
let latest = null;
|
|
89
|
+
for (const claim of attestations.get(subject) ?? []) {
|
|
90
|
+
if (claim.predicate !== `${ATTESTATION_PREDICATE_PREFIX}${topic}`)
|
|
91
|
+
continue;
|
|
92
|
+
if (!('value' in claim.object))
|
|
93
|
+
continue;
|
|
94
|
+
const parts = parseAttestationClaimValue(claim.object.value);
|
|
95
|
+
if (parts === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
if (latest === null || parts.on >= latest.on) {
|
|
98
|
+
latest = { topic, on: parts.on, by: parts.by, source: citation(claim) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return latest;
|
|
102
|
+
};
|
|
103
|
+
const rows = rowIds.map((subject) => {
|
|
104
|
+
const type = typeOf(subject);
|
|
105
|
+
const owner = refsOf(subject, 'yarramate/ownership/owner')[0];
|
|
106
|
+
const description = value(subject, 'yarramate/concept/description');
|
|
107
|
+
return {
|
|
108
|
+
subject,
|
|
109
|
+
name: nameOf(subject),
|
|
110
|
+
kind: kindOf(subject),
|
|
111
|
+
type,
|
|
112
|
+
status: value(subject, 'yarramate/lifecycle/status') ?? null,
|
|
113
|
+
...(description === undefined ? {} : { description }),
|
|
114
|
+
owner: owner === undefined ? null : refOf(owner),
|
|
115
|
+
threatens: type === 'risk' ? targets(subject, 'influence') : [],
|
|
116
|
+
bearsOn: type === 'assumption' ? targets(subject, 'association') : [],
|
|
117
|
+
mitigatedBy: type === 'risk' ? sources(subject, ['influence', 'association']) : [],
|
|
118
|
+
review: latestReview(subject, REVIEW_TOPICS[type]),
|
|
119
|
+
supersedes: [...refsOf(subject, 'yarramate/lineage/supersedes')].sort(),
|
|
120
|
+
groupedBy: sources(subject, ['aggregation']),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
const ids = (predicate) => rows.filter(predicate).map(({ subject }) => subject);
|
|
124
|
+
const unowned = ids((row) => row.owner === null);
|
|
125
|
+
const unmitigated = ids((row) => row.type === 'risk' && row.status === 'current' && row.mitigatedBy.length === 0);
|
|
126
|
+
const unconfirmed = ids((row) => row.type === 'assumption' && row.review === null);
|
|
127
|
+
const unreviewed = ids((row) => row.type === 'risk' && row.review === null);
|
|
128
|
+
return {
|
|
129
|
+
format: 'yarramate/governance/v1',
|
|
130
|
+
workspace,
|
|
131
|
+
summary: {
|
|
132
|
+
rows: rows.length,
|
|
133
|
+
risks: rows.filter(({ type }) => type === 'risk').length,
|
|
134
|
+
assumptions: rows.filter(({ type }) => type === 'assumption').length,
|
|
135
|
+
unowned: unowned.length,
|
|
136
|
+
unmitigated: unmitigated.length,
|
|
137
|
+
unconfirmed: unconfirmed.length,
|
|
138
|
+
unreviewed: unreviewed.length,
|
|
139
|
+
},
|
|
140
|
+
rows,
|
|
141
|
+
gaps: { unowned, unmitigated, unconfirmed, unreviewed },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const escapeCell = (text) => text.replaceAll('|', '\\|');
|
|
145
|
+
const names = (entries) => entries.map(({ name }) => escapeCell(name)).join(', ');
|
|
146
|
+
const list = (ids) => ids.length === 0 ? 'none' : ids.map((id) => `\`${id}\``).join(', ');
|
|
147
|
+
/** The log as one markdown table a person reads, with the gaps under it. */
|
|
148
|
+
export function renderGovernanceMarkdown(log) {
|
|
149
|
+
const lines = ['# Governance log', ''];
|
|
150
|
+
lines.push(`Workspace \`${log.workspace}\`: ${log.summary.risks} risk${log.summary.risks === 1 ? '' : 's'}, ${log.summary.assumptions} assumption${log.summary.assumptions === 1 ? '' : 's'}. Derived from the record; edit the native documents, not this file.`, '');
|
|
151
|
+
if (log.rows.length === 0) {
|
|
152
|
+
lines.push('_No rows._', '');
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
lines.push('| Subject | Type | Status | Owner | Threatens / bears on | Mitigated by | Review | Grouped by |', '|---|---|---|---|---|---|---|---|');
|
|
156
|
+
for (const row of log.rows) {
|
|
157
|
+
const review = row.review === null ? '' : `${row.review.topic} ${row.review.on} by ${escapeCell(row.review.by)}`;
|
|
158
|
+
lines.push(`| ${escapeCell(row.name)} (\`${row.subject}\`) | ${row.type} | ${row.status ?? ''} | ${row.owner === null ? '' : escapeCell(row.owner.name)} | ${names(row.type === 'risk' ? row.threatens : row.bearsOn)} | ${names(row.mitigatedBy)} | ${review} | ${names(row.groupedBy)} |`);
|
|
159
|
+
}
|
|
160
|
+
lines.push('');
|
|
161
|
+
}
|
|
162
|
+
lines.push('## Gaps', '', `- Unowned: ${list(log.gaps.unowned)}`, `- Unmitigated (current risks): ${list(log.gaps.unmitigated)}`, `- Unconfirmed assumptions: ${list(log.gaps.unconfirmed)}`, `- Unreviewed risks: ${list(log.gaps.unreviewed)}`, '');
|
|
163
|
+
return `${lines.join('\n')}\n`;
|
|
164
|
+
}
|
|
@@ -67,6 +67,23 @@ 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;
|
|
81
|
+
/**
|
|
82
|
+
* A reading the endpoints decide (ADR 0160): "threatens" for an
|
|
83
|
+
* influence from a risk, "mitigates" for one into a risk, "bears on" for
|
|
84
|
+
* an association from an assumption. Absent, the kind's own reading.
|
|
85
|
+
*/
|
|
86
|
+
readonly reading?: string;
|
|
70
87
|
readonly from: string;
|
|
71
88
|
readonly to: string;
|
|
72
89
|
readonly name: string | null;
|
|
@@ -84,4 +101,5 @@ export interface CanvasGraph {
|
|
|
84
101
|
readonly nodes: readonly CanvasNode[];
|
|
85
102
|
readonly edges: readonly CanvasEdge[];
|
|
86
103
|
}
|
|
104
|
+
import { type ResponsibilityLetter } from './responsibility-kinds.js';
|
|
87
105
|
export declare function projectGraphForCanvas(graph: SemanticGraph, profileContext: ResolvedProfileContext): CanvasGraph;
|
package/dist/graph-projection.js
CHANGED
|
@@ -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 { contextualReading, 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';
|
|
@@ -160,7 +162,7 @@ const projectConcept = (subjectId, claims, profileContext) => {
|
|
|
160
162
|
attestations,
|
|
161
163
|
};
|
|
162
164
|
};
|
|
163
|
-
const projectRelationship = (relationshipId, allClaims, ownClaims, profileContext) => {
|
|
165
|
+
const projectRelationship = (relationshipId, allClaims, ownClaims, profileContext, conceptKindOf = new Map()) => {
|
|
164
166
|
// A relationship's defining claim is asymmetric: its id is the
|
|
165
167
|
// relationship's subject id, but its own `subject` field is the
|
|
166
168
|
// from-concept, so it lives in the from-concept's claim group, not this
|
|
@@ -170,6 +172,15 @@ 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);
|
|
177
|
+
const endpointLineage = (subject) => {
|
|
178
|
+
const conceptKind = conceptKindOf.get(subject);
|
|
179
|
+
return conceptKind === undefined
|
|
180
|
+
? undefined
|
|
181
|
+
: profileContext.conceptKindLineages.get(conceptKind);
|
|
182
|
+
};
|
|
183
|
+
const reading = contextualReading(endpointLineage(definingClaim.subject), endpointLineage(claimRef(definingClaim)), coreKindLabel);
|
|
173
184
|
const nameClaim = ownClaims.find((claim) => claim.predicate === RELATIONSHIP_NAME_PREDICATE);
|
|
174
185
|
const descriptionClaim = ownClaims.find((claim) => claim.predicate === RELATIONSHIP_DESCRIPTION_PREDICATE);
|
|
175
186
|
const modeClaim = ownClaims.find((claim) => claim.predicate === ACCESS_MODE_PREDICATE);
|
|
@@ -181,7 +192,10 @@ const projectRelationship = (relationshipId, allClaims, ownClaims, profileContex
|
|
|
181
192
|
document: definingClaim.source.path,
|
|
182
193
|
kind,
|
|
183
194
|
kindLabel: kindLabelOf(kind),
|
|
184
|
-
coreKindLabel
|
|
195
|
+
coreKindLabel,
|
|
196
|
+
readingKind: readingKindOf(lineage, coreKindLabel),
|
|
197
|
+
responsibility: responsibilityLetterOf(lineage, kind),
|
|
198
|
+
...(reading === undefined ? {} : { reading }),
|
|
185
199
|
from: definingClaim.subject,
|
|
186
200
|
to: claimRef(definingClaim),
|
|
187
201
|
name: nameClaim === undefined ? null : claimValue(nameClaim),
|
|
@@ -210,6 +224,13 @@ const projectRelationship = (relationshipId, allClaims, ownClaims, profileContex
|
|
|
210
224
|
};
|
|
211
225
|
export function projectGraphForCanvas(graph, profileContext) {
|
|
212
226
|
const claimsBySubject = groupClaimsBySubject(graph.claims);
|
|
227
|
+
// Each concept's authored kind, for the readings an edge's endpoints decide.
|
|
228
|
+
const conceptKindOf = new Map();
|
|
229
|
+
for (const claim of graph.claims) {
|
|
230
|
+
if (claim.predicate === CONCEPT_KIND_PREDICATE && 'value' in claim.object) {
|
|
231
|
+
conceptKindOf.set(claim.subject, claim.object.value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
213
234
|
const nodes = [];
|
|
214
235
|
const edges = [];
|
|
215
236
|
for (const subject of graph.subjects) {
|
|
@@ -218,7 +239,7 @@ export function projectGraphForCanvas(graph, profileContext) {
|
|
|
218
239
|
nodes.push(projectConcept(subject.id, ownClaims, profileContext));
|
|
219
240
|
}
|
|
220
241
|
else {
|
|
221
|
-
edges.push(projectRelationship(subject.id, graph.claims, ownClaims, profileContext));
|
|
242
|
+
edges.push(projectRelationship(subject.id, graph.claims, ownClaims, profileContext, conceptKindOf));
|
|
222
243
|
}
|
|
223
244
|
}
|
|
224
245
|
nodes.sort(compareById);
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ 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';
|
|
15
|
+
export { buildGovernanceLog, renderGovernanceMarkdown, type GovernanceLog, type GovernanceOptions, type GovernanceRef, type GovernanceReview, type GovernanceRow, type GovernanceSource, } from './governance.js';
|
|
16
|
+
export { GOVERNANCE_KINDS, GOVERNANCE_PROFILE, REVIEW_TOPICS, governanceTypeOf, type GovernanceType, } from './governance-kinds.js';
|
|
13
17
|
export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, PatternMembership, PatternPortShape, PatternShape, PatternSlotShape, PatternVacancy, PatternWireShape, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
14
18
|
export { canonicalProjection, evaluateProjection, explainProjection, instanceClosureOf, loadProjection, renderProjectionMarkdown, type ConceptFacet, type ProjectionExclusion, } from './projection.js';
|
|
15
19
|
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,10 @@ 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';
|
|
15
|
+
export { buildGovernanceLog, renderGovernanceMarkdown, } from './governance.js';
|
|
16
|
+
export { GOVERNANCE_KINDS, GOVERNANCE_PROFILE, REVIEW_TOPICS, governanceTypeOf, } from './governance-kinds.js';
|
|
13
17
|
export { canonicalProjection, evaluateProjection, explainProjection, instanceClosureOf, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
14
18
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
|
|
15
19
|
export { createFileSystemStore, } from './source-store.js';
|
package/dist/projection.d.ts
CHANGED
|
@@ -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
|
package/dist/projection.js
CHANGED
|
@@ -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,39 @@
|
|
|
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;
|
|
27
|
+
/**
|
|
28
|
+
* Readings that depend on the SOURCE kind as well as the relationship
|
|
29
|
+
* (ADR 0160): a risk "threatens" what it influences, where anything else
|
|
30
|
+
* "influences" it; an assumption "bears on" what it associates. Keyed by
|
|
31
|
+
* the source kind's identity, then the relationship's core kind, and
|
|
32
|
+
* resolved through the source's lineage so a profile's subkind of `risk`
|
|
33
|
+
* threatens too.
|
|
34
|
+
*/
|
|
35
|
+
export declare const SOURCE_CONTEXTUAL_READING: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
36
|
+
/**
|
|
37
|
+
* Readings that depend on the TARGET kind: a work package, deliverable,
|
|
38
|
+
* constraint or decision "mitigates" the risk it influences. Consulted
|
|
39
|
+
* after the source table, so a risk that influences another risk still
|
|
40
|
+
* "threatens" it.
|
|
41
|
+
*/
|
|
42
|
+
export declare const TARGET_CONTEXTUAL_READING: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
43
|
+
/**
|
|
44
|
+
* The reading an edge speaks when its endpoints decide it, or undefined
|
|
45
|
+
* when the relationship kind alone does (the tables above are consulted
|
|
46
|
+
* source first). `coreKind` is the relationship's core kind label.
|
|
47
|
+
*/
|
|
48
|
+
export declare const contextualReading: (sourceLineage: readonly string[] | undefined, targetLineage: readonly string[] | undefined, coreKind: string) => string | undefined;
|
|
16
49
|
export declare const REVERSED_READING: Readonly<Record<string, string>>;
|
|
17
50
|
/**
|
|
18
51
|
* `acme/p@1#applicationComponent` reads "application component" and
|
|
@@ -25,4 +58,4 @@ export declare const humanizeKind: (kind: string) => string;
|
|
|
25
58
|
* and gets it only where one exists; a kind with no passive reading keeps its
|
|
26
59
|
* active one, so a caller can pass the layering's answer straight through.
|
|
27
60
|
*/
|
|
28
|
-
export declare const relationshipReading: (
|
|
61
|
+
export declare const relationshipReading: (kind: string, reversed?: boolean) => string;
|
|
@@ -25,6 +25,68 @@ 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
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Readings that depend on the SOURCE kind as well as the relationship
|
|
54
|
+
* (ADR 0160): a risk "threatens" what it influences, where anything else
|
|
55
|
+
* "influences" it; an assumption "bears on" what it associates. Keyed by
|
|
56
|
+
* the source kind's identity, then the relationship's core kind, and
|
|
57
|
+
* resolved through the source's lineage so a profile's subkind of `risk`
|
|
58
|
+
* threatens too.
|
|
59
|
+
*/
|
|
60
|
+
export const SOURCE_CONTEXTUAL_READING = {
|
|
61
|
+
'yarramate/policy@0.3#risk': { influence: 'threatens' },
|
|
62
|
+
'yarramate/policy@0.3#assumption': { association: 'bears on' },
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Readings that depend on the TARGET kind: a work package, deliverable,
|
|
66
|
+
* constraint or decision "mitigates" the risk it influences. Consulted
|
|
67
|
+
* after the source table, so a risk that influences another risk still
|
|
68
|
+
* "threatens" it.
|
|
69
|
+
*/
|
|
70
|
+
export const TARGET_CONTEXTUAL_READING = {
|
|
71
|
+
'yarramate/policy@0.3#risk': { influence: 'mitigates' },
|
|
72
|
+
};
|
|
73
|
+
const lookupContextual = (table, lineage, coreKind) => {
|
|
74
|
+
if (lineage === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
for (let index = lineage.length - 1; index >= 0; index -= 1) {
|
|
77
|
+
const phrase = table[lineage[index]]?.[coreKind];
|
|
78
|
+
if (phrase !== undefined)
|
|
79
|
+
return phrase;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The reading an edge speaks when its endpoints decide it, or undefined
|
|
85
|
+
* when the relationship kind alone does (the tables above are consulted
|
|
86
|
+
* source first). `coreKind` is the relationship's core kind label.
|
|
87
|
+
*/
|
|
88
|
+
export const contextualReading = (sourceLineage, targetLineage, coreKind) => lookupContextual(SOURCE_CONTEXTUAL_READING, sourceLineage, coreKind) ??
|
|
89
|
+
lookupContextual(TARGET_CONTEXTUAL_READING, targetLineage, coreKind);
|
|
28
90
|
export const REVERSED_READING = {
|
|
29
91
|
serving: 'served by',
|
|
30
92
|
realization: 'realized by',
|
|
@@ -47,6 +109,7 @@ export const humanizeKind = (kind) => {
|
|
|
47
109
|
* and gets it only where one exists; a kind with no passive reading keeps its
|
|
48
110
|
* active one, so a caller can pass the layering's answer straight through.
|
|
49
111
|
*/
|
|
50
|
-
export const relationshipReading = (
|
|
51
|
-
|
|
52
|
-
|
|
112
|
+
export const relationshipReading = (kind, reversed = false) => (reversed ? REVERSED_READING[kind] : undefined) ??
|
|
113
|
+
EXTENSION_READING[kind] ??
|
|
114
|
+
RELATIONSHIP_READING[kind] ??
|
|
115
|
+
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);
|