yarramate 1.2.0 → 1.4.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 +237 -2
- package/dist/adapters/visual/session-server.js +16 -1
- package/dist/adapters/visual/view-identity.d.ts +1 -1
- package/dist/adapters/visual/view-identity.js +61 -3
- package/dist/adapters/visual/wire.d.ts +30 -0
- package/dist/adapters/visual/workspace-model.d.ts +35 -3
- package/dist/adapters/visual/workspace-model.js +62 -2
- package/dist/ask-command.js +37 -14
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +26 -5
- package/dist/compiler.d.ts +13 -1
- package/dist/compiler.js +646 -0
- package/dist/concept-drafting.d.ts +13 -2
- package/dist/concept-drafting.js +15 -3
- package/dist/design-command.js +30 -4
- package/dist/evidence.js +25 -0
- package/dist/graph-projection.d.ts +10 -0
- package/dist/graph-projection.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/interrogate-command.d.ts +37 -1
- package/dist/interrogate-command.js +93 -42
- package/dist/interrogation-entry.d.ts +1 -1
- package/dist/layout-direction.d.ts +14 -0
- package/dist/layout-direction.js +5 -0
- package/dist/projection.d.ts +22 -6
- package/dist/projection.js +23 -2
- package/dist/relationship-drafting.d.ts +21 -2
- package/dist/relationship-drafting.js +49 -4
- package/dist/subject-references.js +5 -0
- package/dist/visual-app/assets/index-Rkq6smL2.css +1 -0
- package/dist/visual-app/assets/index-yCHtmQUH.js +394 -0
- package/dist/visual-app/index.html +2 -2
- package/dist/visual-app-lib/editor.js +29639 -27514
- package/dist/visual-app-lib/styles.css +1 -1
- package/dist/visual-app-lib/types/adapters/visual/view-identity.d.ts +1 -1
- package/dist/visual-app-lib/types/adapters/visual/wire.d.ts +30 -0
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +35 -3
- package/dist/visual-app-lib/types/compiler.d.ts +13 -1
- package/dist/visual-app-lib/types/concept-drafting.d.ts +13 -2
- package/dist/visual-app-lib/types/graph-projection.d.ts +10 -0
- package/dist/visual-app-lib/types/interrogate-command.d.ts +207 -0
- package/dist/visual-app-lib/types/layout-direction.d.ts +14 -0
- package/dist/visual-app-lib/types/projection.d.ts +22 -6
- package/dist/visual-app-lib/types/relationship-drafting.d.ts +21 -2
- package/dist/visual-app-lib/types/subject-identity.d.ts +34 -0
- package/dist/visual-app-lib/types/visual-app/App.d.ts +27 -2
- package/dist/visual-app-lib/types/visual-app/badges.d.ts +1 -0
- package/dist/visual-app-lib/types/visual-app/connection-panel.d.ts +9 -1
- package/dist/visual-app-lib/types/visual-app/context-menu-model.d.ts +15 -6
- package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +77 -6
- package/dist/visual-app-lib/types/visual-app/kind-palette.d.ts +37 -0
- package/dist/visual-app-lib/types/visual-app/local-host.d.ts +42 -0
- package/dist/visual-app-lib/types/visual-app/mount.d.ts +73 -2
- package/dist/visual-app-lib/types/visual-app/open-questions.d.ts +15 -0
- 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 +8 -1
- package/dist/visual-app-lib/types/visual-app/save-view.d.ts +11 -5
- package/dist/visual-app-lib/types/visual-app/shipped-catalogue.d.ts +10 -0
- package/dist/visual-app-lib/types/visual-app/state.d.ts +15 -4
- package/dist/visual-app-lib/types/visual-app/subject-draft-panel.d.ts +13 -1
- package/dist/visual-app-lib/types/visual-app/subject-filter.d.ts +35 -0
- package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +8 -0
- package/dist/visual-app-lib/types/visual-app/view-tree-model.d.ts +42 -9
- package/dist/visual-app-lib/types/visual-app/view-tree.d.ts +11 -2
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +81 -11
- package/dist/visual-app-lib/types/workspace.d.ts +2 -0
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.js +1 -0
- package/docs/CONSUMING-YARRAMATE.md +81 -0
- package/package.json +2 -1
- package/schema/yarramate-design-step.schema.json +26 -0
- package/schema/yarramate-document.schema.json +14 -0
- package/schema/yarramate-interrogation-report.schema.json +31 -0
- package/schema/yarramate-pattern.schema.json +133 -0
- package/schema/yarramate-projection.schema.json +9 -0
- package/schema/yarramate-question-catalogue.schema.json +34 -0
- package/schema/yarramate-visual-graph.schema.json +9 -0
- package/schema/yarramate-workspace.schema.json +4 -0
- package/skills/yarramate-architecture/SKILL.md +5 -0
- package/dist/visual-app/assets/index-CcmfL3oY.js +0 -394
- package/dist/visual-app/assets/index-DLutIWES.css +0 -1
|
@@ -9,8 +9,14 @@ import type { YarramateOperation } from './operations.js';
|
|
|
9
9
|
* about a name produces worse ids than a transliteration of the name does.
|
|
10
10
|
* Returning null rather than a placeholder keeps a subject called `"???"` from
|
|
11
11
|
* landing as `subject-1`, which nothing could later be traced back from.
|
|
12
|
+
*
|
|
13
|
+
* `reserved` carries ids the graph does not know yet: a staged-but-uncommitted
|
|
14
|
+
* draft never enters the rendered graph, so without it a second subject whose
|
|
15
|
+
* name slugs to the same id re-proposed it and the editor's replace-by-target
|
|
16
|
+
* staging silently swallowed the first (#315) - the identical blind spot
|
|
17
|
+
* `proposeRelationshipId` had before #306's fix, and the identical way out.
|
|
12
18
|
*/
|
|
13
|
-
export declare const proposeConceptId: (graph: CanvasGraph, name: string) => string | null;
|
|
19
|
+
export declare const proposeConceptId: (graph: CanvasGraph, name: string, reserved?: Iterable<string>) => string | null;
|
|
14
20
|
/**
|
|
15
21
|
* The operation that lands a new subject, or `null` when the draft is one no
|
|
16
22
|
* document could accept.
|
|
@@ -19,9 +25,14 @@ export declare const proposeConceptId: (graph: CanvasGraph, name: string) => str
|
|
|
19
25
|
* a kind outside it here, rather than trusting the caller's palette, is the
|
|
20
26
|
* same posture `draftRelationship` takes: the guarantee has to hold for any
|
|
21
27
|
* caller, not only one that filtered first.
|
|
28
|
+
*
|
|
29
|
+
* A caller holding drafts the graph has not landed yet - an editor with a
|
|
30
|
+
* pending changeset - passes their ids as `reserved`, so a second subject
|
|
31
|
+
* slugging to a taken id steps to `-2` instead of colliding with the first
|
|
32
|
+
* (#315).
|
|
22
33
|
*/
|
|
23
34
|
export declare const draftConcept: (graph: CanvasGraph, input: {
|
|
24
35
|
readonly name: string;
|
|
25
36
|
readonly kind: string;
|
|
26
37
|
readonly document: string;
|
|
27
|
-
}, kinds: readonly string[]) => YarramateOperation | null;
|
|
38
|
+
}, kinds: readonly string[], reserved?: Iterable<string>) => YarramateOperation | null;
|
package/dist/concept-drafting.js
CHANGED
|
@@ -17,8 +17,14 @@ const ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
|
17
17
|
* about a name produces worse ids than a transliteration of the name does.
|
|
18
18
|
* Returning null rather than a placeholder keeps a subject called `"???"` from
|
|
19
19
|
* landing as `subject-1`, which nothing could later be traced back from.
|
|
20
|
+
*
|
|
21
|
+
* `reserved` carries ids the graph does not know yet: a staged-but-uncommitted
|
|
22
|
+
* draft never enters the rendered graph, so without it a second subject whose
|
|
23
|
+
* name slugs to the same id re-proposed it and the editor's replace-by-target
|
|
24
|
+
* staging silently swallowed the first (#315) - the identical blind spot
|
|
25
|
+
* `proposeRelationshipId` had before #306's fix, and the identical way out.
|
|
20
26
|
*/
|
|
21
|
-
export const proposeConceptId = (graph, name) => {
|
|
27
|
+
export const proposeConceptId = (graph, name, reserved = []) => {
|
|
22
28
|
const base = name
|
|
23
29
|
.normalize('NFKD')
|
|
24
30
|
// The marks NFKD split off, dropped rather than treated as boundaries:
|
|
@@ -38,6 +44,7 @@ export const proposeConceptId = (graph, name) => {
|
|
|
38
44
|
const taken = new Set([
|
|
39
45
|
...graph.nodes.map((node) => node.id),
|
|
40
46
|
...graph.edges.map((edge) => edge.id),
|
|
47
|
+
...reserved,
|
|
41
48
|
]);
|
|
42
49
|
if (!taken.has(base))
|
|
43
50
|
return base;
|
|
@@ -55,8 +62,13 @@ export const proposeConceptId = (graph, name) => {
|
|
|
55
62
|
* a kind outside it here, rather than trusting the caller's palette, is the
|
|
56
63
|
* same posture `draftRelationship` takes: the guarantee has to hold for any
|
|
57
64
|
* caller, not only one that filtered first.
|
|
65
|
+
*
|
|
66
|
+
* A caller holding drafts the graph has not landed yet - an editor with a
|
|
67
|
+
* pending changeset - passes their ids as `reserved`, so a second subject
|
|
68
|
+
* slugging to a taken id steps to `-2` instead of colliding with the first
|
|
69
|
+
* (#315).
|
|
58
70
|
*/
|
|
59
|
-
export const draftConcept = (graph, input, kinds) => {
|
|
71
|
+
export const draftConcept = (graph, input, kinds, reserved = []) => {
|
|
60
72
|
if (input.document === '')
|
|
61
73
|
return null;
|
|
62
74
|
if (!kinds.includes(input.kind))
|
|
@@ -64,7 +76,7 @@ export const draftConcept = (graph, input, kinds) => {
|
|
|
64
76
|
const name = input.name.trim();
|
|
65
77
|
if (name === '')
|
|
66
78
|
return null;
|
|
67
|
-
const id = proposeConceptId(graph, name);
|
|
79
|
+
const id = proposeConceptId(graph, name, reserved);
|
|
68
80
|
if (id === null)
|
|
69
81
|
return null;
|
|
70
82
|
return {
|
package/dist/design-command.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { parseDocument } from 'yaml';
|
|
5
5
|
import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
|
|
6
6
|
import { compileWorkspaceWithProfileContext, } from './compiler.js';
|
|
7
|
+
import { loadEvidence } from './evidence.js';
|
|
7
8
|
import { evaluateCatalogue, loadQuestionCatalogue, renderQuestion, } from './interrogate-command.js';
|
|
8
9
|
import { evaluateProjection } from './projection.js';
|
|
9
10
|
import { renderBrief } from './brief.js';
|
|
@@ -202,6 +203,20 @@ export function runDesignCommand(options, cwd) {
|
|
|
202
203
|
})));
|
|
203
204
|
if (!compilation.ok)
|
|
204
205
|
return failed(compilation.diagnostics);
|
|
206
|
+
// The evidence overlay rides along for the one condition that reads
|
|
207
|
+
// it (unchallenged-evidence). A workspace declaring no evidence
|
|
208
|
+
// passes an empty overlay — known to be empty, which keeps that
|
|
209
|
+
// condition quiet — rather than an absent one.
|
|
210
|
+
const evidenceObservations = [];
|
|
211
|
+
for (const path of workspace.evidence) {
|
|
212
|
+
const loadedEvidence = loadEvidence({
|
|
213
|
+
path,
|
|
214
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
215
|
+
});
|
|
216
|
+
if (!loadedEvidence.ok)
|
|
217
|
+
return failed(loadedEvidence.diagnostics);
|
|
218
|
+
evidenceObservations.push(...loadedEvidence.evidence.observations);
|
|
219
|
+
}
|
|
205
220
|
if (subjectFilter !== undefined) {
|
|
206
221
|
const known = new Set(compilation.graph.subjects.map(({ id }) => id));
|
|
207
222
|
if (!known.has(subjectFilter)) {
|
|
@@ -212,7 +227,7 @@ export function runDesignCommand(options, cwd) {
|
|
|
212
227
|
};
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
|
-
const report = evaluateCatalogue(loadedCatalogue.catalogue, compilation.graph, compilation.profileContext);
|
|
230
|
+
const report = evaluateCatalogue(loadedCatalogue.catalogue, compilation.graph, compilation.profileContext, evidenceObservations);
|
|
216
231
|
const askPlainById = new Map(loadedCatalogue.catalogue.questions.flatMap((question) => question.askPlain === undefined
|
|
217
232
|
? []
|
|
218
233
|
: [[question.id, question.askPlain]]));
|
|
@@ -267,9 +282,20 @@ export function runDesignCommand(options, cwd) {
|
|
|
267
282
|
'',
|
|
268
283
|
];
|
|
269
284
|
if (step === null) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
285
|
+
// "No open questions" has two causes and only one of them is complete
|
|
286
|
+
// (#334). Every wave gated shut asks NOTHING, so a blank model reaches
|
|
287
|
+
// zero without a single question having been put - and claiming the
|
|
288
|
+
// model answers everything the catalogue asks is then flatly false
|
|
289
|
+
// about a catalogue that asked nothing. Completion inferred from an
|
|
290
|
+
// empty set is the same fault the wave rail and the report renderer
|
|
291
|
+
// each carried; this is the sentence an agent reads to decide it is
|
|
292
|
+
// done, so it is the worst place for it.
|
|
293
|
+
const asked = report.waves.some((wave) => wave.questions.length > 0);
|
|
294
|
+
lines.push(!asked
|
|
295
|
+
? 'No wave has opened yet: this catalogue asks nothing until the model has substance. Declare a subject and run this again.'
|
|
296
|
+
: subjectFilter === undefined
|
|
297
|
+
? 'Interview complete: no open questions. The model answers everything the catalogue asks.'
|
|
298
|
+
: `Interview complete for ${subjectFilter}: no open questions touch it.`);
|
|
273
299
|
}
|
|
274
300
|
else {
|
|
275
301
|
// --facilitate prefers the workshop phrasing and falls back to the
|
package/dist/evidence.js
CHANGED
|
@@ -27,6 +27,31 @@ export function loadEvidence(source) {
|
|
|
27
27
|
...(observation.key === undefined || observation.value === undefined
|
|
28
28
|
? {}
|
|
29
29
|
: { key: observation.key, value: observation.value }),
|
|
30
|
+
// Provenance survives normalization: reconcile counts a
|
|
31
|
+
// not-observed naming no search (ADR 0107) and interrogation's
|
|
32
|
+
// unchallenged-evidence reads recorded searches (ADR 0120), so a
|
|
33
|
+
// load path that dropped them made both read every overlay as
|
|
34
|
+
// probe-free however carefully its author recorded one.
|
|
35
|
+
...(observation.searched === undefined
|
|
36
|
+
? {}
|
|
37
|
+
: {
|
|
38
|
+
searched: observation.searched.map((probe) => 'glob' in probe
|
|
39
|
+
? { glob: probe.glob }
|
|
40
|
+
: {
|
|
41
|
+
grep: probe.grep,
|
|
42
|
+
...(probe.paths === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { paths: [...probe.paths] }),
|
|
45
|
+
}),
|
|
46
|
+
}),
|
|
47
|
+
...(observation.measured === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: {
|
|
50
|
+
measured: observation.measured.map(({ value, method }) => ({
|
|
51
|
+
value,
|
|
52
|
+
method,
|
|
53
|
+
})),
|
|
54
|
+
}),
|
|
30
55
|
evidence: {
|
|
31
56
|
uri: observation.evidence.uri,
|
|
32
57
|
...(observation.evidence.message === undefined
|
|
@@ -14,6 +14,16 @@ export interface CanvasNode {
|
|
|
14
14
|
* profile kind it was authored as.
|
|
15
15
|
*/
|
|
16
16
|
readonly coreKindLabel: string;
|
|
17
|
+
/**
|
|
18
|
+
* The core relationship kinds this node's PATTERN ports, or `[]` where its
|
|
19
|
+
* kind has no pattern with ports (#268 phase 3, ADR 0124).
|
|
20
|
+
*
|
|
21
|
+
* A macro edge needs both ends to port its kind, so an editor offering a
|
|
22
|
+
* palette between two instances intersects the two lists. Two raw groupings
|
|
23
|
+
* permit ten of the eleven kinds, which is no guidance at all; this is what
|
|
24
|
+
* restores the narrowing the relationship table gives everywhere else.
|
|
25
|
+
*/
|
|
26
|
+
readonly portKinds: readonly string[];
|
|
17
27
|
readonly layer: Layer | null;
|
|
18
28
|
readonly aspect: Aspect | null;
|
|
19
29
|
readonly name: string;
|
package/dist/graph-projection.js
CHANGED
|
@@ -119,6 +119,7 @@ const projectConcept = (subjectId, claims, profileContext) => {
|
|
|
119
119
|
kind,
|
|
120
120
|
kindLabel: kindLabelOf(kind),
|
|
121
121
|
coreKindLabel: kindLabelOf(profileContext.conceptKindLineages.get(kind)?.[0] ?? kind),
|
|
122
|
+
portKinds: profileContext.patternPortKinds.get(kind) ?? [],
|
|
122
123
|
layer: profileContext.conceptKindLayers.get(kind) ?? null,
|
|
123
124
|
aspect: profileContext.conceptKindAspects.get(kind) ?? null,
|
|
124
125
|
name: claimValue(nameClaim),
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, ty
|
|
|
15
15
|
export type { LifecycleStatus, ProjectionDefinition, ProjectionLoadResult, ProjectionResult, } from './projection.js';
|
|
16
16
|
export { createFileSystemStore, type PendingWrite, type SourceStore, type StoredSource, type WriteConflict, type WriteOutcome, } from './source-store.js';
|
|
17
17
|
export { applyOperations, landOperations, posixDirectoryOf, type ApplyInput, type ApplyOutcome, } from './apply-command.js';
|
|
18
|
-
export { connectableKinds, draftRelationship, proposeRelationshipId, } from './relationship-drafting.js';
|
|
18
|
+
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
19
19
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
20
20
|
export { deletionBlockers, describeDeletion, draftDeletion, type DeletionBlocker, } from './deletion-drafting.js';
|
|
21
|
-
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
|
21
|
+
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export { canonicalProjection, evaluateProjection, explainProjection, loadProject
|
|
|
13
13
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
|
|
14
14
|
export { createFileSystemStore, } from './source-store.js';
|
|
15
15
|
export { applyOperations, landOperations, posixDirectoryOf, } from './apply-command.js';
|
|
16
|
-
export { connectableKinds, draftRelationship, proposeRelationshipId, } from './relationship-drafting.js';
|
|
16
|
+
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
17
17
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
18
18
|
export { deletionBlockers, describeDeletion, draftDeletion, } from './deletion-drafting.js';
|
|
19
19
|
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, } from './interrogate-command.js';
|
|
@@ -85,7 +85,28 @@ export type CatalogueCondition = {
|
|
|
85
85
|
readonly condition: 'unconstrained-kind';
|
|
86
86
|
} | {
|
|
87
87
|
readonly condition: 'unscoped-succession';
|
|
88
|
+
} | {
|
|
89
|
+
readonly condition: 'unchallenged-evidence';
|
|
90
|
+
} | {
|
|
91
|
+
readonly condition: 'has-any-subject';
|
|
88
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* One observation from the workspace's evidence overlay, reduced to what
|
|
95
|
+
* interrogation reads: the result, and whether a search was recorded with
|
|
96
|
+
* it. The only condition that reads the overlay is `unchallenged-evidence`;
|
|
97
|
+
* every other condition reads the compiled graph alone, and the overlay
|
|
98
|
+
* never influences which subjects a selector matches.
|
|
99
|
+
*
|
|
100
|
+
* Shaped structurally rather than importing {@link EvidenceObservation} so
|
|
101
|
+
* the pure engine entry (`./interrogation-entry`) keeps owning its whole
|
|
102
|
+
* input surface: a caller passes
|
|
103
|
+
* `evidenceDocuments.flatMap(({ observations }) => observations)` and the
|
|
104
|
+
* wider evidence shape is never dragged in.
|
|
105
|
+
*/
|
|
106
|
+
export interface CatalogueEvidenceObservation {
|
|
107
|
+
readonly result: 'confirmed' | 'contradicted' | 'unknown' | 'not-observed';
|
|
108
|
+
readonly searched?: readonly unknown[];
|
|
109
|
+
}
|
|
89
110
|
export interface CatalogueQuestion {
|
|
90
111
|
readonly id: string;
|
|
91
112
|
readonly wave: string;
|
|
@@ -112,6 +133,11 @@ export interface QuestionCatalogue {
|
|
|
112
133
|
readonly id: string;
|
|
113
134
|
readonly name: string;
|
|
114
135
|
readonly description?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Conditions that must all hold before this wave opens (#334, ADR 0125).
|
|
138
|
+
* Absent means always open, which is what every wave did before this.
|
|
139
|
+
*/
|
|
140
|
+
readonly opensWhen?: readonly CatalogueCondition[];
|
|
115
141
|
}[];
|
|
116
142
|
readonly questions: readonly CatalogueQuestion[];
|
|
117
143
|
}
|
|
@@ -142,6 +168,16 @@ export interface ReportQuestion {
|
|
|
142
168
|
export interface ReportWave {
|
|
143
169
|
readonly id: string;
|
|
144
170
|
readonly name: string;
|
|
171
|
+
/**
|
|
172
|
+
* Whether the wave's gate is met (#334, ADR 0125). A wave with no
|
|
173
|
+
* `opensWhen` is always open.
|
|
174
|
+
*
|
|
175
|
+
* A wave reported `false` carries NO questions and contributes nothing to
|
|
176
|
+
* the summary. Its questions are premature rather than answered, and a
|
|
177
|
+
* progress rail that counted them as answered would flatter itself exactly
|
|
178
|
+
* where someone is most likely to trust it.
|
|
179
|
+
*/
|
|
180
|
+
readonly opened: boolean;
|
|
145
181
|
readonly questions: readonly ReportQuestion[];
|
|
146
182
|
}
|
|
147
183
|
export interface InterrogationSummary {
|
|
@@ -159,7 +195,7 @@ export interface InterrogationReport {
|
|
|
159
195
|
readonly waves: readonly ReportWave[];
|
|
160
196
|
}
|
|
161
197
|
export declare const renderQuestion: (template: string, subjectId: string, subjectName: string | undefined, counterparts?: readonly string[]) => string;
|
|
162
|
-
export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext): Omit<InterrogationReport, 'workspace'>;
|
|
198
|
+
export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext, evidence?: readonly CatalogueEvidenceObservation[]): Omit<InterrogationReport, 'workspace'>;
|
|
163
199
|
export type CatalogueLoadResult = {
|
|
164
200
|
readonly ok: true;
|
|
165
201
|
readonly catalogue: QuestionCatalogue;
|
|
@@ -236,8 +236,34 @@ const linkageHits = (index, condition, subjectId, profileContext) => {
|
|
|
236
236
|
return counterparts.some((counterpart) => kindMatches(index.kindOf.get(counterpart), condition.counterpartKinds, matching, profileContext));
|
|
237
237
|
});
|
|
238
238
|
};
|
|
239
|
-
const conditionHolds = (index, condition, subjectId, profileContext) => {
|
|
239
|
+
const conditionHolds = (index, condition, subjectId, profileContext, evidence) => {
|
|
240
240
|
switch (condition.condition) {
|
|
241
|
+
case 'has-any-subject':
|
|
242
|
+
// The guard a late wave needs to say "only once the model has
|
|
243
|
+
// substance" (#334). An empty model is not an architecture at rest -
|
|
244
|
+
// ADR 0120's reading, which stays true for a model that HAS started -
|
|
245
|
+
// it has not begun, and asking it how the planned architecture becomes
|
|
246
|
+
// real greets someone ahead of question one.
|
|
247
|
+
return index.concepts.size > 0;
|
|
248
|
+
case 'unchallenged-evidence':
|
|
249
|
+
// Fires where the overlay records observations and every one is a
|
|
250
|
+
// frictionless confirmation: no contradicted, unknown, or
|
|
251
|
+
// not-observed result, and no recorded search. A discovery that
|
|
252
|
+
// never records anything but success never tested a claim it might
|
|
253
|
+
// fail — 39 of 39 GitLab observations said confirmed while Praefect
|
|
254
|
+
// sat declared upstream and absent from the tree (#272). A recorded
|
|
255
|
+
// search closes it even on a confirmed result, because a
|
|
256
|
+
// confirmation of a negative claim rests on exactly the empty
|
|
257
|
+
// search ADR 0107 made auditable; so does any honest non-confirmed
|
|
258
|
+
// result. An empty overlay stays quiet: with no observations there
|
|
259
|
+
// is no inspection to interrogate. An absent overlay also stays
|
|
260
|
+
// quiet — the caller did not supply one, so its diversity is
|
|
261
|
+
// unknown, not absent, the same rule `unconstrained-kind` applies
|
|
262
|
+
// to a missing profile context.
|
|
263
|
+
return (evidence !== undefined &&
|
|
264
|
+
evidence.length > 0 &&
|
|
265
|
+
evidence.every(({ result, searched }) => result === 'confirmed' &&
|
|
266
|
+
(searched === undefined || searched.length === 0)));
|
|
241
267
|
case 'missing-claim':
|
|
242
268
|
return !(index.claimsBySubject.get(subjectId) ?? []).some(({ predicate }) => predicate === condition.predicate);
|
|
243
269
|
case 'unscoped-succession': {
|
|
@@ -425,61 +451,76 @@ const describeCounterparts = (index, question, subjectId) => {
|
|
|
425
451
|
return name === undefined ? id : `${name} (${id})`;
|
|
426
452
|
});
|
|
427
453
|
};
|
|
428
|
-
export function evaluateCatalogue(catalogue, graph, profileContext) {
|
|
454
|
+
export function evaluateCatalogue(catalogue, graph, profileContext, evidence) {
|
|
429
455
|
const index = indexGraph(graph);
|
|
430
456
|
let open = 0;
|
|
431
457
|
let openQuestions = 0;
|
|
432
458
|
const applicableQuestions = catalogue.questions.filter((question) => questionIsApplicable(question, graph.profiles));
|
|
459
|
+
const waveOpens = (wave) => wave.opensWhen === undefined ||
|
|
460
|
+
wave.opensWhen.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
|
|
433
461
|
const waves = catalogue.waves.map((wave) => ({
|
|
434
462
|
id: wave.id,
|
|
435
463
|
name: wave.name,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
464
|
+
opened: waveOpens(wave),
|
|
465
|
+
// A closed wave asks nothing. Its questions are not evaluated at all,
|
|
466
|
+
// rather than evaluated and reported closed - the latter would say they
|
|
467
|
+
// had been asked and answered - so they reach neither the report nor the
|
|
468
|
+
// summary.
|
|
469
|
+
questions: !waveOpens(wave)
|
|
470
|
+
? []
|
|
471
|
+
: applicableQuestions
|
|
472
|
+
.filter((question) => question.wave === wave.id)
|
|
473
|
+
.map((question) => {
|
|
474
|
+
const base = {
|
|
475
|
+
id: question.id,
|
|
476
|
+
scope: question.scope,
|
|
477
|
+
authority: question.authority,
|
|
478
|
+
question: question.question.trim(),
|
|
479
|
+
materiality: question.materiality.trim(),
|
|
480
|
+
resolution: question.resolution.trim(),
|
|
481
|
+
trigger: question.trigger,
|
|
482
|
+
...(question.since === undefined ? {} : { since: question.since }),
|
|
483
|
+
};
|
|
484
|
+
if (question.scope === 'workspace') {
|
|
485
|
+
const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
|
|
486
|
+
if (isOpen) {
|
|
487
|
+
open += 1;
|
|
488
|
+
openQuestions += 1;
|
|
489
|
+
}
|
|
490
|
+
return { ...base, open: isOpen };
|
|
454
491
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
};
|
|
475
|
-
}),
|
|
492
|
+
const matches = selectSubjects(index, question.subjects, profileContext).filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence)));
|
|
493
|
+
if (matches.length === 0) {
|
|
494
|
+
return { ...base, open: false };
|
|
495
|
+
}
|
|
496
|
+
open += matches.length;
|
|
497
|
+
openQuestions += 1;
|
|
498
|
+
return {
|
|
499
|
+
...base,
|
|
500
|
+
open: true,
|
|
501
|
+
subjects: matches.map((id) => {
|
|
502
|
+
const name = index.nameOf.get(id);
|
|
503
|
+
return {
|
|
504
|
+
id,
|
|
505
|
+
...(name === undefined ? {} : { name }),
|
|
506
|
+
question: renderQuestion(question.question, id, name, describeCounterparts(index, question, id)),
|
|
507
|
+
};
|
|
508
|
+
}),
|
|
509
|
+
};
|
|
510
|
+
}),
|
|
476
511
|
}));
|
|
477
512
|
return {
|
|
478
513
|
format: 'yarramate/interrogation-report/v1',
|
|
479
514
|
catalogue: `${catalogue.id}@${catalogue.version}`,
|
|
480
515
|
semantics: INTERROGATION_SEMANTICS_VERSION,
|
|
481
516
|
summary: {
|
|
482
|
-
|
|
517
|
+
// Questions in OPENED waves only (#334, ADR 0125). A closed wave's
|
|
518
|
+
// questions have not been asked, so counting them in the denominator
|
|
519
|
+
// would report them as answered - "3 of 51" reading as forty-eight
|
|
520
|
+
// done when forty-eight were never put. The denominator grows as the
|
|
521
|
+
// model gains substance and waves open, which is the interview
|
|
522
|
+
// revealing itself rather than a rail filling up.
|
|
523
|
+
questions: waves.reduce((total, wave) => total + wave.questions.length, 0),
|
|
483
524
|
openQuestions,
|
|
484
525
|
open,
|
|
485
526
|
},
|
|
@@ -519,6 +560,16 @@ export function renderInterrogationReport(report) {
|
|
|
519
560
|
];
|
|
520
561
|
for (const wave of report.waves) {
|
|
521
562
|
lines.push('', `== ${wave.name} ==`);
|
|
563
|
+
// A wave that has not opened must not read like one whose questions are
|
|
564
|
+
// all closed (#334). Both carry no OPEN questions, and a bare heading with
|
|
565
|
+
// nothing under it is the more flattering of the two readings: "nothing
|
|
566
|
+
// outstanding here" rather than "nobody has been asked anything here".
|
|
567
|
+
// The same shape - completion inferred from an empty set - was found in a
|
|
568
|
+
// consuming product's wave rail on the same day.
|
|
569
|
+
if (!wave.opened) {
|
|
570
|
+
lines.push(' not yet — this wave has not opened');
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
522
573
|
for (const question of wave.questions) {
|
|
523
574
|
if (!question.open) {
|
|
524
575
|
lines.push(` closed ${question.id}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
|
1
|
+
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which way a view runs its layers, kept in a module that imports nothing.
|
|
3
|
+
*
|
|
4
|
+
* Here rather than in `projection.ts` for the reason `./nesting.ts` gives: the
|
|
5
|
+
* browser needs the value and not only the type, and `projection.ts` drags Ajv
|
|
6
|
+
* and the projection schema in for one constant. `projection.ts` re-exports
|
|
7
|
+
* both.
|
|
8
|
+
*/
|
|
9
|
+
export type LayoutDirection = 'top-down' | 'left-right';
|
|
10
|
+
/**
|
|
11
|
+
* How a view runs when it does not say. Top-down is the behaviour that
|
|
12
|
+
* shipped, and it is what ArchiMate's layer bands read as (ADR 0121).
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_DIRECTION: LayoutDirection;
|
package/dist/projection.d.ts
CHANGED
|
@@ -6,6 +6,16 @@ export interface ProjectionDefinition {
|
|
|
6
6
|
readonly version: string;
|
|
7
7
|
readonly query: {
|
|
8
8
|
readonly subjects?: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* Subjects this query would otherwise select and the author has taken out
|
|
11
|
+
* (#267, ADR 0122). A facet view states a rule, and every interesting rule
|
|
12
|
+
* has an exception someone would rather state than abandon the rule for;
|
|
13
|
+
* this is where that exception is written down instead of being the silent
|
|
14
|
+
* absence a hand-enumerated list produces. Applied after every other facet
|
|
15
|
+
* AND after `relationships: connected` expansion, so an excluded subject is
|
|
16
|
+
* out whichever way it would have come back in.
|
|
17
|
+
*/
|
|
18
|
+
readonly exclude?: readonly string[];
|
|
9
19
|
readonly documents?: readonly string[];
|
|
10
20
|
readonly kinds?: readonly string[];
|
|
11
21
|
readonly layers?: readonly string[];
|
|
@@ -24,12 +34,12 @@ export interface ProjectionDefinition {
|
|
|
24
34
|
readonly description?: string;
|
|
25
35
|
readonly layout?: 'layered';
|
|
26
36
|
/**
|
|
27
|
-
* Read by the LikeC4 export for its
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* Which way this view runs its layers. Read by the LikeC4 export for its
|
|
38
|
+
* `autoLayout` and by the canvas for ELK's `elk.direction` (ADR 0121); a
|
|
39
|
+
* view that says nothing runs `top-down`, which is what ArchiMate's layer
|
|
40
|
+
* bands read as.
|
|
31
41
|
*/
|
|
32
|
-
readonly direction?:
|
|
42
|
+
readonly direction?: LayoutDirection;
|
|
33
43
|
/**
|
|
34
44
|
* The relationship kinds that draw as nesting in this view, in precedence
|
|
35
45
|
* order (ADR 0101). Absent means `['composition']`, which is the behaviour
|
|
@@ -64,6 +74,12 @@ export interface ProjectionDefinition {
|
|
|
64
74
|
*/
|
|
65
75
|
export { DEFAULT_NESTING, type NestingKind } from './nesting.js';
|
|
66
76
|
import type { NestingKind } from './nesting.js';
|
|
77
|
+
/**
|
|
78
|
+
* Which way a view runs, and the default. Split out for the same reason as the
|
|
79
|
+
* nesting vocabulary above, and re-exported here on the same terms (ADR 0121).
|
|
80
|
+
*/
|
|
81
|
+
export { DEFAULT_DIRECTION, type LayoutDirection } from './layout-direction.js';
|
|
82
|
+
import type { LayoutDirection } from './layout-direction.js';
|
|
67
83
|
export type ProjectionQuery = ProjectionDefinition['query'];
|
|
68
84
|
export interface ProjectionResult {
|
|
69
85
|
readonly format: 'yarramate/projection-result/v1';
|
|
@@ -86,7 +102,7 @@ export declare function canonicalProjection(projection: ProjectionDefinition): P
|
|
|
86
102
|
* A facet of a query, named the way the query names it. What
|
|
87
103
|
* {@link explainProjection} reports as the reason a subject is not in a view.
|
|
88
104
|
*/
|
|
89
|
-
export type ConceptFacet = 'states' | 'subjects' | 'documents' | 'kinds' | 'layers' | 'statuses' | 'excludeStatuses' | 'owners' | 'constraints';
|
|
105
|
+
export type ConceptFacet = 'exclude' | 'states' | 'subjects' | 'documents' | 'kinds' | 'layers' | 'statuses' | 'excludeStatuses' | 'owners' | 'constraints';
|
|
90
106
|
/** One subject a query dropped, and the facet that dropped it. */
|
|
91
107
|
export interface ProjectionExclusion {
|
|
92
108
|
readonly id: string;
|
package/dist/projection.js
CHANGED
|
@@ -14,6 +14,11 @@ const validateProjection = new Ajv2020({ allErrors: true }).compile(projectionSc
|
|
|
14
14
|
* and a schema, which is a great deal of bundle for one constant (ADR 0101).
|
|
15
15
|
*/
|
|
16
16
|
export { DEFAULT_NESTING } from './nesting.js';
|
|
17
|
+
/**
|
|
18
|
+
* Which way a view runs, and the default. Split out for the same reason as the
|
|
19
|
+
* nesting vocabulary above, and re-exported here on the same terms (ADR 0121).
|
|
20
|
+
*/
|
|
21
|
+
export { DEFAULT_DIRECTION } from './layout-direction.js';
|
|
17
22
|
export function loadProjection(source) {
|
|
18
23
|
const loaded = loadSourceDocument(source, validateProjection, 'Projection');
|
|
19
24
|
return loaded.ok
|
|
@@ -124,7 +129,13 @@ const conceptSelector = (graph, projection, profileContext) => {
|
|
|
124
129
|
? undefined
|
|
125
130
|
: profileContext?.conceptKindLayers.get(kind);
|
|
126
131
|
// Ordered the way a query declares its facets, so "the first reason" is
|
|
127
|
-
// the one a reader would reach first themselves
|
|
132
|
+
// the one a reader would reach first themselves - except the explicit
|
|
133
|
+
// exception, which outranks every rule: when someone has written the
|
|
134
|
+
// subject down as taken out, that IS the first reason, whatever else
|
|
135
|
+
// would also have dropped it (#267).
|
|
136
|
+
if (query.exclude?.includes(id) === true) {
|
|
137
|
+
return 'exclude';
|
|
138
|
+
}
|
|
128
139
|
if (query.states !== undefined &&
|
|
129
140
|
(architectureStateIds.has(id) || !participatesInSelectedState(id))) {
|
|
130
141
|
return 'states';
|
|
@@ -201,6 +212,12 @@ export function explainProjection(graph, projection, profileContext) {
|
|
|
201
212
|
export function evaluateProjection(graph, projection, profileContext) {
|
|
202
213
|
const { droppedBy, architectureStateIds, participatesInSelectedState } = conceptSelector(graph, projection, profileContext);
|
|
203
214
|
const endpointExcluded = (id) => {
|
|
215
|
+
// An exclusion is final (#267, ADR 0122). Dropping the subject from the
|
|
216
|
+
// initial selection alone would not be: `relationships: connected` adds
|
|
217
|
+
// the far end of every relationship it draws, so an excluded subject would
|
|
218
|
+
// walk back in by the other end of a relationship to one that stayed.
|
|
219
|
+
if (projection.query.exclude?.includes(id) === true)
|
|
220
|
+
return true;
|
|
204
221
|
if (!participatesInSelectedState(id))
|
|
205
222
|
return true;
|
|
206
223
|
const status = claimValue(graph.claims, id, 'yarramate/lifecycle/status');
|
|
@@ -221,7 +238,11 @@ export function evaluateProjection(graph, projection, profileContext) {
|
|
|
221
238
|
continue;
|
|
222
239
|
const relationship = graph.claims.find((claim) => claim.id === subject.id);
|
|
223
240
|
const relationshipStatus = claimValue(graph.claims, subject.id, 'yarramate/lifecycle/status');
|
|
224
|
-
if (
|
|
241
|
+
if (
|
|
242
|
+
// A relationship can be taken out by name too: `exclude` names
|
|
243
|
+
// subjects, and a relationship is a subject.
|
|
244
|
+
projection.query.exclude?.includes(subject.id) === true ||
|
|
245
|
+
relationship === undefined ||
|
|
225
246
|
!('ref' in relationship.object) ||
|
|
226
247
|
(projection.query.excludeStatuses !== undefined &&
|
|
227
248
|
relationshipStatus !== undefined &&
|
|
@@ -33,8 +33,16 @@ export declare const connectableKinds: (graph: CanvasGraph, fromId: string, toId
|
|
|
33
33
|
* relationship kind is a single lowercase word. A collision takes a numeric
|
|
34
34
|
* suffix rather than a hash, because the id is authored text a human will read
|
|
35
35
|
* in a diff.
|
|
36
|
+
*
|
|
37
|
+
* `reserved` carries ids the graph does not know yet: a staged-but-uncommitted
|
|
38
|
+
* draft never enters the rendered graph, so without it a second relationship
|
|
39
|
+
* between the same pair re-proposed the identical id and the editor's
|
|
40
|
+
* replace-by-target staging silently swallowed the first (#306). The schema
|
|
41
|
+
* places no uniqueness on the (from, kind, to) triple - parallel relationships
|
|
42
|
+
* with distinct ids compile cleanly - so the id proposal is the only place the
|
|
43
|
+
* collision can be stepped past.
|
|
36
44
|
*/
|
|
37
|
-
export declare const proposeRelationshipId: (graph: CanvasGraph, fromId: string, kind: RelationshipKind, toId: string) => string;
|
|
45
|
+
export declare const proposeRelationshipId: (graph: CanvasGraph, fromId: string, kind: RelationshipKind, toId: string, reserved?: Iterable<string>) => string;
|
|
38
46
|
/**
|
|
39
47
|
* The operation that lands a drafted relationship, or `null` when the draft is
|
|
40
48
|
* one the table does not permit.
|
|
@@ -47,5 +55,16 @@ export declare const proposeRelationshipId: (graph: CanvasGraph, fromId: string,
|
|
|
47
55
|
* relationship has to live somewhere, both endpoints are equally defensible,
|
|
48
56
|
* and the source is where a reader looking for what this thing does would go
|
|
49
57
|
* first.
|
|
58
|
+
*
|
|
59
|
+
* A caller holding drafts the graph has not landed yet - an editor with a
|
|
60
|
+
* pending changeset - passes their ids as `reserved`, so a second parallel
|
|
61
|
+
* relationship steps to `-2` instead of colliding with the first (#306).
|
|
62
|
+
*/
|
|
63
|
+
export declare const draftRelationship: (graph: CanvasGraph, fromId: string, kind: RelationshipKind, toId: string, reserved?: Iterable<string>) => YarramateOperation | null;
|
|
64
|
+
/**
|
|
65
|
+
* The ids a pending changeset already claims, for `proposeRelationshipId`'s
|
|
66
|
+
* `reserved` parameter. Every operation that names a subject id reserves it -
|
|
67
|
+
* an update's id is already in the graph and reserving it twice is harmless,
|
|
68
|
+
* while an add's id is exactly the one the graph cannot know yet.
|
|
50
69
|
*/
|
|
51
|
-
export declare const
|
|
70
|
+
export declare const stagedSubjectIds: (operations: readonly YarramateOperation[]) => readonly string[];
|