yarramate 0.15.0 → 0.16.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.
@@ -1 +1 @@
1
- export { observeGraphify, type GraphifyGraph, type GraphifyObservationIssue, type GraphifyObservationResult, } from './graphify.js';
1
+ export { observeGraphify, type GraphifyObservationIssue, type GraphifyGraph, type GraphifyObservationResult, } from './graphify.js';
@@ -4,6 +4,11 @@ export interface GraphifyGraph {
4
4
  readonly nodes: readonly {
5
5
  readonly id: string;
6
6
  }[];
7
+ readonly edges?: readonly {
8
+ readonly id: string;
9
+ readonly source: string;
10
+ readonly target: string;
11
+ }[];
7
12
  }
8
13
  export interface GraphifyObservationIssue {
9
14
  readonly code: 'YMG101' | 'YMG102';
@@ -233,6 +233,6 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
233
233
  return [` style ${external} { color amber }`];
234
234
  }
235
235
  return [];
236
- })), ' autoLayout LeftRight', ' }', '}', '');
236
+ })), ` autoLayout ${projection.presentation?.direction === 'top-down' ? 'TopBottom' : 'LeftRight'}`, ' }', '}', '');
237
237
  return { ok: true, source: lines.join('\n') };
238
238
  }
@@ -46,6 +46,7 @@ export interface SemanticGraph {
46
46
  export interface ResolvedProfileContext {
47
47
  readonly conceptKindLineages: ReadonlyMap<string, readonly string[]>;
48
48
  readonly relationshipKindLineages: ReadonlyMap<string, readonly string[]>;
49
+ readonly conceptKindLayers: ReadonlyMap<string, string>;
49
50
  }
50
51
  export type CompilationResult = {
51
52
  readonly ok: true;
package/dist/compiler.js CHANGED
@@ -10,6 +10,24 @@ const coreProfile = 'yarramate/core@0.1';
10
10
  const Ajv2020 = Ajv2020Module.default;
11
11
  const validateDocument = new Ajv2020({ allErrors: true }).compile(documentSchema);
12
12
  const validateProfile = new Ajv2020({ allErrors: true }).compile(profileSchema);
13
+ const immutableMap = (entries) => {
14
+ const backing = new Map(entries);
15
+ const facade = {
16
+ get: backing.get.bind(backing),
17
+ has: backing.has.bind(backing),
18
+ forEach(callback, thisArg) {
19
+ backing.forEach((value, key) => callback.call(thisArg, value, key, facade));
20
+ },
21
+ entries: backing.entries.bind(backing),
22
+ keys: backing.keys.bind(backing),
23
+ values: backing.values.bind(backing),
24
+ [Symbol.iterator]: backing[Symbol.iterator].bind(backing),
25
+ get size() {
26
+ return backing.size;
27
+ },
28
+ };
29
+ return Object.freeze(facade);
30
+ };
13
31
  const compareById = (left, right) => left.id.localeCompare(right.id);
14
32
  const presenceClaimId = (subject, state) => `${subject}~present-in-${Buffer.from(state, 'utf8').toString('hex')}`;
15
33
  // Alternative labels and distinctness records are unordered sets, not
@@ -64,6 +82,7 @@ function compileWorkspaceResolved(sources) {
64
82
  const resolved = {
65
83
  identity: `${coreProfile}#${kind.id}`,
66
84
  aspect: kind.aspect,
85
+ layer: kind.layer,
67
86
  lineage: [`${coreProfile}#${kind.id}`],
68
87
  ...(kind.rigidity === undefined ? {} : { rigidity: kind.rigidity }),
69
88
  };
@@ -226,6 +245,7 @@ function compileWorkspaceResolved(sources) {
226
245
  const resolved = {
227
246
  identity: `${identity}#${kind.id}`,
228
247
  aspect: parent.aspect,
248
+ layer: kind.layer ?? parent.layer,
229
249
  lineage: [...parent.lineage, `${identity}#${kind.id}`],
230
250
  ...(kind.rigidity === undefined ? {} : { rigidity: kind.rigidity }),
231
251
  };
@@ -1267,12 +1287,21 @@ function compileWorkspaceResolved(sources) {
1267
1287
  return {
1268
1288
  ok: true,
1269
1289
  profileContext: {
1270
- conceptKindLineages: new Map([...conceptKindByIdentity]
1290
+ conceptKindLineages: immutableMap([...conceptKindByIdentity]
1271
1291
  .sort(([left], [right]) => left.localeCompare(right))
1272
- .map(([identity, kind]) => [identity, kind.lineage])),
1273
- relationshipKindLineages: new Map([...relationshipKindByIdentity]
1292
+ .map(([identity, kind]) => [
1293
+ identity,
1294
+ Object.freeze([...kind.lineage]),
1295
+ ])),
1296
+ relationshipKindLineages: immutableMap([...relationshipKindByIdentity]
1297
+ .sort(([left], [right]) => left.localeCompare(right))
1298
+ .map(([identity, kind]) => [
1299
+ identity,
1300
+ Object.freeze([...kind.lineage]),
1301
+ ])),
1302
+ conceptKindLayers: immutableMap([...conceptKindByIdentity]
1274
1303
  .sort(([left], [right]) => left.localeCompare(right))
1275
- .map(([identity, kind]) => [identity, kind.lineage])),
1304
+ .map(([identity, kind]) => [identity, kind.layer])),
1276
1305
  },
1277
1306
  graph: {
1278
1307
  format: 'yarramate/graph/v2',
package/dist/index.d.ts CHANGED
@@ -8,6 +8,6 @@ export { constraintExpectsPredicate, reconcileEvidenceReports, type AssertedRela
8
8
  export { deriveAttestationStaleness } from './attestation-staleness.js';
9
9
  export { buildRtm, renderRtmMarkdown, type RequirementsTraceabilityMatrix, type RtmAttestation, type RtmContextEntry, type RtmDescopedEntry, type RtmEvidenceVerdict, type RtmLineageEntry, type RtmRealizer, type RtmRow, type RtmSource, } from './rtm.js';
10
10
  export type { CompilationResult, ContextualCompilationResult, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
11
- export { evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
11
+ export { canonicalProjection, evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
12
12
  export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, type AdapterMapping, type AdapterMappingLoadResult, type AdapterMappingValidationResult, type AdapterMappingsValidationResult, type AdapterSubjectMapping, } from './adapter-mapping.js';
13
13
  export type { LifecycleStatus, ProjectionDefinition, ProjectionLoadResult, ProjectionResult, } from './projection.js';
package/dist/index.js CHANGED
@@ -7,5 +7,5 @@ export { evaluateEvidence, evaluateEvidenceWorkspace, loadEvidence, } from './ev
7
7
  export { constraintExpectsPredicate, reconcileEvidenceReports, } from './reconciliation.js';
8
8
  export { deriveAttestationStaleness } from './attestation-staleness.js';
9
9
  export { buildRtm, renderRtmMarkdown, } from './rtm.js';
10
- export { evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
10
+ export { canonicalProjection, evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
11
11
  export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
@@ -8,6 +8,7 @@ export interface ProjectionDefinition {
8
8
  readonly subjects?: readonly string[];
9
9
  readonly documents?: readonly string[];
10
10
  readonly kinds?: readonly string[];
11
+ readonly layers?: readonly string[];
11
12
  readonly statuses?: readonly LifecycleStatus[];
12
13
  readonly excludeStatuses?: readonly LifecycleStatus[];
13
14
  readonly states?: readonly string[];
@@ -21,6 +22,12 @@ export interface ProjectionDefinition {
21
22
  readonly presentation?: {
22
23
  readonly title?: string;
23
24
  readonly description?: string;
25
+ readonly layout?: 'layered' | 'radial' | 'force';
26
+ readonly direction?: 'top-down' | 'left-right';
27
+ readonly seed?: string;
28
+ readonly showLifecycle?: boolean;
29
+ readonly showEvidence?: boolean;
30
+ readonly showOwnership?: boolean;
24
31
  };
25
32
  }
26
33
  export interface ProjectionResult {
@@ -39,6 +46,7 @@ export type ProjectionLoadResult = {
39
46
  readonly diagnostics: readonly Diagnostic[];
40
47
  };
41
48
  export declare function loadProjection(source: WorkspaceSource): ProjectionLoadResult;
49
+ export declare function canonicalProjection(projection: ProjectionDefinition): ProjectionDefinition;
42
50
  export declare function evaluateProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext): ProjectionResult;
43
51
  export declare function renderProjectionMarkdown(result: ProjectionResult, profileContext?: ResolvedProfileContext): string;
44
52
  export declare function renderBudgetedContext(result: ProjectionResult, budgetTokens: number): string;
@@ -11,6 +11,44 @@ export function loadProjection(source) {
11
11
  ? { ok: true, projection: loaded.document.value }
12
12
  : loaded;
13
13
  }
14
+ export function canonicalProjection(projection) {
15
+ const query = projection.query;
16
+ const presentation = projection.presentation;
17
+ return {
18
+ format: projection.format,
19
+ id: projection.id,
20
+ version: projection.version,
21
+ query: {
22
+ ...(query.subjects === undefined ? {} : { subjects: [...query.subjects].sort() }),
23
+ ...(query.documents === undefined ? {} : { documents: [...query.documents].sort() }),
24
+ ...(query.kinds === undefined ? {} : { kinds: [...query.kinds].sort() }),
25
+ ...(query.layers === undefined ? {} : { layers: [...query.layers].sort() }),
26
+ ...(query.statuses === undefined ? {} : { statuses: [...query.statuses].sort() }),
27
+ ...(query.excludeStatuses === undefined ? {} : { excludeStatuses: [...query.excludeStatuses].sort() }),
28
+ ...(query.states === undefined ? {} : { states: [...query.states].sort() }),
29
+ ...(query.owners === undefined ? {} : { owners: [...query.owners].sort() }),
30
+ ...(query.constraints === undefined ? {} : { constraints: [...query.constraints].sort() }),
31
+ ...(query.relationshipKinds === undefined ? {} : { relationshipKinds: [...query.relationshipKinds].sort() }),
32
+ ...(query.kindMatching === undefined ? {} : { kindMatching: query.kindMatching }),
33
+ ...(query.relationships === undefined ? {} : { relationships: query.relationships }),
34
+ ...(query.isolatedConcepts === undefined ? {} : { isolatedConcepts: query.isolatedConcepts }),
35
+ },
36
+ ...(presentation === undefined
37
+ ? {}
38
+ : {
39
+ presentation: {
40
+ ...(presentation.title === undefined ? {} : { title: presentation.title }),
41
+ ...(presentation.description === undefined ? {} : { description: presentation.description }),
42
+ ...(presentation.layout === undefined ? {} : { layout: presentation.layout }),
43
+ ...(presentation.direction === undefined ? {} : { direction: presentation.direction }),
44
+ ...(presentation.seed === undefined ? {} : { seed: presentation.seed }),
45
+ ...(presentation.showLifecycle === undefined ? {} : { showLifecycle: presentation.showLifecycle }),
46
+ ...(presentation.showEvidence === undefined ? {} : { showEvidence: presentation.showEvidence }),
47
+ ...(presentation.showOwnership === undefined ? {} : { showOwnership: presentation.showOwnership }),
48
+ },
49
+ }),
50
+ };
51
+ }
14
52
  const claimValue = (claims, subject, predicate) => {
15
53
  const object = claims.find((claim) => claim.subject === subject && claim.predicate === predicate)?.object;
16
54
  return object !== undefined && 'value' in object ? object.value : undefined;
@@ -40,6 +78,14 @@ export function evaluateProjection(graph, projection, profileContext) {
40
78
  return (presence.length === 0 ||
41
79
  presence.some((state) => selectedStateIds.includes(state)));
42
80
  };
81
+ const endpointExcluded = (id) => {
82
+ if (!participatesInSelectedState(id))
83
+ return true;
84
+ const status = claimValue(graph.claims, id, 'yarramate/lifecycle/status');
85
+ return (status !== undefined &&
86
+ projection.query.excludeStatuses?.includes(status) ===
87
+ true);
88
+ };
43
89
  const initiallySelectedConceptIds = new Set(graph.subjects
44
90
  .filter(({ type }) => type === 'concept')
45
91
  .filter(({ id }) => {
@@ -62,6 +108,10 @@ export function evaluateProjection(graph, projection, profileContext) {
62
108
  profileContext?.conceptKindLineages
63
109
  .get(kind)
64
110
  ?.includes(selectedKind) === true)))) &&
111
+ (projection.query.layers === undefined ||
112
+ (kind !== undefined &&
113
+ profileContext?.conceptKindLayers.get(kind) !== undefined &&
114
+ projection.query.layers.includes(profileContext.conceptKindLayers.get(kind)))) &&
65
115
  (projection.query.statuses === undefined ||
66
116
  (status !== undefined &&
67
117
  projection.query.statuses.includes(status))) &&
@@ -100,16 +150,6 @@ export function evaluateProjection(graph, projection, profileContext) {
100
150
  }
101
151
  const sourceSelected = initiallySelectedConceptIds.has(relationship.subject);
102
152
  const targetSelected = initiallySelectedConceptIds.has(relationship.object.ref);
103
- // excludeStatuses also vetoes connected expansion: an excluded
104
- // concept is never pulled in as a neighbour, and edges touching
105
- // one are dropped rather than left dangling.
106
- const endpointExcluded = (id) => {
107
- if (projection.query.excludeStatuses === undefined)
108
- return false;
109
- const endpointStatus = claimValue(graph.claims, id, 'yarramate/lifecycle/status');
110
- return (endpointStatus !== undefined &&
111
- projection.query.excludeStatuses.includes(endpointStatus));
112
- };
113
153
  if (endpointExcluded(relationship.subject) ||
114
154
  endpointExcluded(relationship.object.ref)) {
115
155
  continue;
@@ -168,6 +208,24 @@ export function evaluateProjection(graph, projection, profileContext) {
168
208
  ...(projection.presentation.description === undefined
169
209
  ? {}
170
210
  : { description: projection.presentation.description }),
211
+ ...(projection.presentation.layout === undefined
212
+ ? {}
213
+ : { layout: projection.presentation.layout }),
214
+ ...(projection.presentation.direction === undefined
215
+ ? {}
216
+ : { direction: projection.presentation.direction }),
217
+ ...(projection.presentation.seed === undefined
218
+ ? {}
219
+ : { seed: projection.presentation.seed }),
220
+ ...(projection.presentation.showLifecycle === undefined
221
+ ? {}
222
+ : { showLifecycle: projection.presentation.showLifecycle }),
223
+ ...(projection.presentation.showEvidence === undefined
224
+ ? {}
225
+ : { showEvidence: projection.presentation.showEvidence }),
226
+ ...(projection.presentation.showOwnership === undefined
227
+ ? {}
228
+ : { showOwnership: projection.presentation.showOwnership }),
171
229
  },
172
230
  }),
173
231
  documents: graph.documents.filter(({ id }) => selectedDocuments.has(id)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -80,6 +80,9 @@
80
80
  },
81
81
  "rigidity": {
82
82
  "$ref": "#/$defs/rigidity"
83
+ },
84
+ "layer": {
85
+ "$ref": "#/$defs/layer"
83
86
  }
84
87
  }
85
88
  },
@@ -115,6 +118,18 @@
115
118
  }
116
119
  }
117
120
  }
118
- }
121
+ },
122
+ "layer": {
123
+ "enum": [
124
+ "motivation",
125
+ "strategy",
126
+ "business",
127
+ "application",
128
+ "technology",
129
+ "physical",
130
+ "implementation",
131
+ "composite"
132
+ ]
133
+ }
119
134
  }
120
135
  }
@@ -24,6 +24,25 @@
24
24
  "description": {
25
25
  "type": "string",
26
26
  "minLength": 1
27
+ },
28
+ "layout": {
29
+ "enum": ["layered", "radial", "force"]
30
+ },
31
+ "direction": {
32
+ "enum": ["top-down", "left-right"]
33
+ },
34
+ "seed": {
35
+ "type": "string",
36
+ "minLength": 1
37
+ },
38
+ "showLifecycle": {
39
+ "type": "boolean"
40
+ },
41
+ "showEvidence": {
42
+ "type": "boolean"
43
+ },
44
+ "showOwnership": {
45
+ "type": "boolean"
27
46
  }
28
47
  }
29
48
  },
@@ -42,6 +42,14 @@
42
42
  "$ref": "#/$defs/qualifiedKind"
43
43
  }
44
44
  },
45
+ "layers": {
46
+ "type": "array",
47
+ "minItems": 1,
48
+ "uniqueItems": true,
49
+ "items": {
50
+ "$ref": "#/$defs/layer"
51
+ }
52
+ },
45
53
  "statuses": {
46
54
  "type": "array",
47
55
  "uniqueItems": true,
@@ -109,8 +117,36 @@
109
117
  },
110
118
  "description": {
111
119
  "$ref": "#/$defs/nonEmptyText"
120
+ },
121
+ "layout": {
122
+ "enum": ["layered", "radial", "force"]
123
+ },
124
+ "direction": {
125
+ "enum": ["top-down", "left-right"]
126
+ },
127
+ "seed": {
128
+ "$ref": "#/$defs/nonEmptyText"
129
+ },
130
+ "showLifecycle": {
131
+ "type": "boolean"
132
+ },
133
+ "showEvidence": {
134
+ "type": "boolean"
135
+ },
136
+ "showOwnership": {
137
+ "type": "boolean"
112
138
  }
113
- }
139
+ },
140
+ "allOf": [
141
+ {
142
+ "if": {
143
+ "required": ["layout"]
144
+ },
145
+ "then": {
146
+ "required": ["seed"]
147
+ }
148
+ }
149
+ ]
114
150
  }
115
151
  },
116
152
  "$defs": {
@@ -129,6 +165,18 @@
129
165
  "nonEmptyText": {
130
166
  "type": "string",
131
167
  "minLength": 1
132
- }
168
+ },
169
+ "layer": {
170
+ "enum": [
171
+ "motivation",
172
+ "strategy",
173
+ "business",
174
+ "application",
175
+ "technology",
176
+ "physical",
177
+ "implementation",
178
+ "composite"
179
+ ]
180
+ }
133
181
  }
134
182
  }