yarramate 1.7.0 → 1.9.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/dist/adapters/visual/session-server.js +3 -0
- package/dist/adapters/visual/workspace-model.d.ts +4 -0
- package/dist/adapters/visual/workspace-model.js +1 -1
- package/dist/artifact-coverage.d.ts +13 -0
- package/dist/artifact-coverage.js +58 -0
- package/dist/ask-command.js +3 -3
- package/dist/cli.js +6 -1
- package/dist/compiler.d.ts +23 -0
- package/dist/compiler.js +23 -1
- package/dist/design-command.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/interrogate-command.d.ts +36 -1
- package/dist/interrogate-command.js +26 -6
- package/dist/interrogation-entry.d.ts +1 -1
- package/dist/reconciliation.d.ts +21 -1
- package/dist/reconciliation.js +93 -2
- package/dist/visual-app/assets/index-1MLdJYji.css +1 -0
- package/dist/visual-app/assets/index-DsH9ve0w.js +394 -0
- package/dist/visual-app/index.html +2 -2
- package/dist/visual-app-lib/editor.js +25859 -26143
- package/dist/visual-app-lib/styles.css +1 -1
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +4 -0
- package/dist/visual-app-lib/types/compiler.d.ts +23 -0
- package/dist/visual-app-lib/types/interrogate-command.d.ts +36 -1
- package/dist/visual-app-lib/types/visual-app/connection-panel.d.ts +25 -1
- package/dist/visual-app-lib/types/visual-app/local-host.d.ts +11 -1
- package/dist/visual-app-lib/types/workspace.d.ts +8 -0
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.js +35 -13
- package/docs/CONSUMING-YARRAMATE.md +14 -0
- package/package.json +1 -1
- package/schema/yarramate-question-catalogue.schema.json +30 -0
- package/schema/yarramate-reconciliation-report.schema.json +21 -1
- package/schema/yarramate-workspace.schema.json +4 -0
- package/dist/visual-app/assets/index-C42oLqXj.js +0 -394
- package/dist/visual-app/assets/index-Rkq6smL2.css +0 -1
|
@@ -620,6 +620,9 @@ export const startVisualServer = async (options) => {
|
|
|
620
620
|
compiledWorkspace = {
|
|
621
621
|
graph: compiled.graph,
|
|
622
622
|
profileContext: compiled.profileContext,
|
|
623
|
+
// Threaded whole (ADR 0131): a narrow copy here is how a slot
|
|
624
|
+
// question would silently never fire in the embedded pane.
|
|
625
|
+
patternMemberships: compiled.patternMemberships,
|
|
623
626
|
};
|
|
624
627
|
const workspaceModel = renderedWorkspaceOf(compiledWorkspace, views, {
|
|
625
628
|
authority: rendered.authority,
|
|
@@ -4,6 +4,7 @@ import type { ResolvedWorkspace } from "../../workspace.js";
|
|
|
4
4
|
import type { VisualDiagnostic, VisualViewOperation } from "./protocol-contract.js";
|
|
5
5
|
import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
6
6
|
import type { ResolvedProfileContext, SemanticGraph } from "../../compiler.js";
|
|
7
|
+
import { type CataloguePatternMembership } from "../../interrogate-command.js";
|
|
7
8
|
import type { VisualKindOption, VisualViewSummary } from "./protocol-contract.js";
|
|
8
9
|
import type { VisualInterrogationOverlay, VisualRenderedModel } from "./wire.js";
|
|
9
10
|
/**
|
|
@@ -57,6 +58,8 @@ export interface DismissedQuestion {
|
|
|
57
58
|
export declare const interrogationOverlayOf: (compiled: {
|
|
58
59
|
readonly graph: SemanticGraph;
|
|
59
60
|
readonly profileContext: ResolvedProfileContext;
|
|
61
|
+
/** From the compilation (ADR 0131); absent, slot questions stay quiet. */
|
|
62
|
+
readonly patternMemberships?: readonly CataloguePatternMembership[];
|
|
60
63
|
},
|
|
61
64
|
/**
|
|
62
65
|
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
@@ -90,6 +93,7 @@ dismissed?: readonly DismissedQuestion[]) => VisualInterrogationOverlay | undefi
|
|
|
90
93
|
export declare const renderedWorkspaceOf: (compiled: {
|
|
91
94
|
readonly graph: SemanticGraph;
|
|
92
95
|
readonly profileContext: ResolvedProfileContext;
|
|
96
|
+
readonly patternMemberships?: readonly CataloguePatternMembership[];
|
|
93
97
|
}, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary" | "interrogation">, catalogue?: {
|
|
94
98
|
readonly path: string;
|
|
95
99
|
readonly source: string;
|
|
@@ -64,7 +64,7 @@ dismissed = []) => {
|
|
|
64
64
|
const composed = composeCatalogues(Array.isArray(catalogue) ? catalogue : [catalogue]);
|
|
65
65
|
if (!composed.ok)
|
|
66
66
|
return undefined;
|
|
67
|
-
const report = evaluateCatalogue(composed.composed.catalogue, compiled.graph, compiled.profileContext, undefined, composed.composed.catalogues);
|
|
67
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compiled.graph, compiled.profileContext, undefined, composed.composed.catalogues, compiled.patternMemberships);
|
|
68
68
|
const dismissedEverywhere = new Set(dismissed
|
|
69
69
|
.filter(({ subject }) => subject === undefined)
|
|
70
70
|
.map(({ questionId }) => questionId));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CoverageScopePattern {
|
|
2
|
+
readonly pattern: string;
|
|
3
|
+
/** Repository-relative files the pattern selected, sorted. */
|
|
4
|
+
readonly artifacts: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export type ArtifactCoverage = {
|
|
7
|
+
readonly assessed: false;
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly assessed: true;
|
|
11
|
+
readonly scope: readonly CoverageScopePattern[];
|
|
12
|
+
};
|
|
13
|
+
export declare function deriveArtifactCoverage(manifestDirectory: string, patterns: readonly string[] | undefined): ArtifactCoverage;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { globSync } from 'node:fs';
|
|
3
|
+
import { sep } from 'node:path';
|
|
4
|
+
const runGit = (cwd, args) => spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8' });
|
|
5
|
+
export function deriveArtifactCoverage(manifestDirectory, patterns) {
|
|
6
|
+
if (patterns === undefined) {
|
|
7
|
+
return {
|
|
8
|
+
assessed: false,
|
|
9
|
+
reason: 'the workspace manifest declares no coverage scope',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
// The root is the git toplevel of the manifest's directory, never the
|
|
13
|
+
// process cwd: the same command must report the same coverage wherever it
|
|
14
|
+
// was invoked (the #216 bug shape), and the repository boundary is git's
|
|
15
|
+
// to draw, not a directory-layout guess.
|
|
16
|
+
const toplevel = runGit(manifestDirectory, [
|
|
17
|
+
'rev-parse',
|
|
18
|
+
'--show-toplevel',
|
|
19
|
+
]);
|
|
20
|
+
if (toplevel.status !== 0) {
|
|
21
|
+
return {
|
|
22
|
+
assessed: false,
|
|
23
|
+
reason: 'the workspace does not live in a git repository',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const root = toplevel.stdout.trim();
|
|
27
|
+
// An artifact is any file git can see: tracked, or untracked and not
|
|
28
|
+
// ignored. Tracked-only would blind the report to exactly the newest
|
|
29
|
+
// files — the recurrence this feature exists to catch (ADR 0130).
|
|
30
|
+
const listed = runGit(root, [
|
|
31
|
+
'ls-files',
|
|
32
|
+
'-z',
|
|
33
|
+
'--cached',
|
|
34
|
+
'--others',
|
|
35
|
+
'--exclude-standard',
|
|
36
|
+
]);
|
|
37
|
+
if (listed.status !== 0) {
|
|
38
|
+
return {
|
|
39
|
+
assessed: false,
|
|
40
|
+
reason: `git ls-files failed: ${(listed.stderr ?? '').trim()}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const visible = new Set((listed.stdout ?? '').split('\0').filter((path) => path.length > 0));
|
|
44
|
+
// Glob matches are intersected with git's view, so a symlink escaping the
|
|
45
|
+
// repository or a build tree git ignores cannot enter the artifact set
|
|
46
|
+
// however broad the glob. The intersection also drops directories: git
|
|
47
|
+
// lists files only.
|
|
48
|
+
return {
|
|
49
|
+
assessed: true,
|
|
50
|
+
scope: patterns.map((pattern) => ({
|
|
51
|
+
pattern,
|
|
52
|
+
artifacts: globSync(pattern, { cwd: root })
|
|
53
|
+
.map((path) => path.split(sep).join('/'))
|
|
54
|
+
.filter((path) => visible.has(path))
|
|
55
|
+
.sort((left, right) => left.localeCompare(right)),
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/ask-command.js
CHANGED
|
@@ -475,7 +475,7 @@ export function runAskCommand(options, cwd) {
|
|
|
475
475
|
}, workspace, cwd), compilation.profileContext);
|
|
476
476
|
if (!composed.ok)
|
|
477
477
|
return failed(composed.diagnostics);
|
|
478
|
-
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues);
|
|
478
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues, compilation.patternMemberships);
|
|
479
479
|
const result = {
|
|
480
480
|
format: 'yarramate/ask-result/v1',
|
|
481
481
|
workspace: workspace.id,
|
|
@@ -719,7 +719,7 @@ export function runAskCommand(options, cwd) {
|
|
|
719
719
|
evidenceObservations.push(...loaded.evidence.observations);
|
|
720
720
|
}
|
|
721
721
|
const report = {
|
|
722
|
-
...evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues),
|
|
722
|
+
...evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues, compilation.patternMemberships),
|
|
723
723
|
workspace: workspace.id,
|
|
724
724
|
};
|
|
725
725
|
// Field by field, to fix key ORDER in the emitted JSON. Every optional
|
|
@@ -1021,7 +1021,7 @@ export function runAskCommand(options, cwd) {
|
|
|
1021
1021
|
return failed(loaded.diagnostics);
|
|
1022
1022
|
evidenceDocuments.push(loaded.evidence);
|
|
1023
1023
|
}
|
|
1024
|
-
const report = evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues);
|
|
1024
|
+
const report = evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues, compilation.patternMemberships);
|
|
1025
1025
|
const openQuestions = [];
|
|
1026
1026
|
for (const wave of report.waves) {
|
|
1027
1027
|
for (const question of wave.questions) {
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { runExportCommand } from './export-command.js';
|
|
|
10
10
|
import { runApplyCommand } from './apply-cli.js';
|
|
11
11
|
import { runDesignCommand } from './design-command.js';
|
|
12
12
|
import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
13
|
+
import { deriveArtifactCoverage } from './artifact-coverage.js';
|
|
13
14
|
import { deriveAttestationStaleness } from './attestation-staleness.js';
|
|
14
15
|
import { reconcileEvidenceReports } from './reconciliation.js';
|
|
15
16
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
@@ -84,9 +85,13 @@ const runReconciliation = (options, cwd) => {
|
|
|
84
85
|
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
85
86
|
documentId: documentIdByPath.get(path) ?? path,
|
|
86
87
|
})));
|
|
88
|
+
// Coverage anchors on the manifest's own directory, not the process
|
|
89
|
+
// cwd, so the same command reports the same coverage wherever it was
|
|
90
|
+
// invoked (ADR 0130).
|
|
91
|
+
const coverage = deriveArtifactCoverage(dirname(resolve(cwd, workspacePath)), loadedWorkspace.manifest.coverage);
|
|
87
92
|
return {
|
|
88
93
|
exitCode: 0,
|
|
89
|
-
stdout: `${JSON.stringify(reconcileEvidenceReports(loadedWorkspace.workspace.id, evaluation.reports, compilation.graph, staleness), null, 2)}\n`,
|
|
94
|
+
stdout: `${JSON.stringify(reconcileEvidenceReports(loadedWorkspace.workspace.id, evaluation.reports, compilation.graph, staleness, coverage), null, 2)}\n`,
|
|
90
95
|
stderr: '',
|
|
91
96
|
};
|
|
92
97
|
}
|
package/dist/compiler.d.ts
CHANGED
|
@@ -94,9 +94,31 @@ export interface ResolvedProfileContext {
|
|
|
94
94
|
*/
|
|
95
95
|
readonly permittedRelationshipKinds: (fromKindIdentity: string, toKindIdentity: string) => ReadonlySet<RelationshipKind> | undefined;
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* One slot of one pattern instance, and the subject bound into it
|
|
99
|
+
* (ADR 0131). Compile CONTEXT, not graph content: `parts` binds existing
|
|
100
|
+
* subjects (#268), the binding is consumed during expansion, and the graph
|
|
101
|
+
* stays indistinguishable from a hand-authored one — so the compile result
|
|
102
|
+
* is the only place this fact survives. `pattern` is the kind identity
|
|
103
|
+
* (`yarrasys/api-led@1.0#api`), the naming ADR 0129 chose: identity that
|
|
104
|
+
* travels, never a document path.
|
|
105
|
+
*/
|
|
106
|
+
export interface PatternMembership {
|
|
107
|
+
readonly member: string;
|
|
108
|
+
readonly slot: string;
|
|
109
|
+
readonly instance: string;
|
|
110
|
+
readonly pattern: string;
|
|
111
|
+
}
|
|
97
112
|
export type CompilationResult = {
|
|
98
113
|
readonly ok: true;
|
|
99
114
|
readonly graph: SemanticGraph;
|
|
115
|
+
/**
|
|
116
|
+
* Optional in the type although the compiler always emits it: this
|
|
117
|
+
* shape is published, and a required addition is free for readers
|
|
118
|
+
* and a break for constructors. Read it as `?? []` — and thread it
|
|
119
|
+
* to `evaluateCatalogue`, or `fills-pattern-slot` never fires.
|
|
120
|
+
*/
|
|
121
|
+
readonly patternMemberships?: readonly PatternMembership[];
|
|
100
122
|
} | {
|
|
101
123
|
readonly ok: false;
|
|
102
124
|
readonly diagnostics: readonly Diagnostic[];
|
|
@@ -105,6 +127,7 @@ export type ContextualCompilationResult = {
|
|
|
105
127
|
readonly ok: true;
|
|
106
128
|
readonly graph: SemanticGraph;
|
|
107
129
|
readonly profileContext: ResolvedProfileContext;
|
|
130
|
+
readonly patternMemberships?: readonly PatternMembership[];
|
|
108
131
|
} | {
|
|
109
132
|
readonly ok: false;
|
|
110
133
|
readonly diagnostics: readonly Diagnostic[];
|
package/dist/compiler.js
CHANGED
|
@@ -2332,8 +2332,24 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2332
2332
|
if (diagnostics.length > 0) {
|
|
2333
2333
|
return diagnosticFailure(diagnostics);
|
|
2334
2334
|
}
|
|
2335
|
+
// Membership survives the compile as context (ADR 0131): one entry per
|
|
2336
|
+
// bound slot, sorted so the emission is deterministic. Always emitted,
|
|
2337
|
+
// possibly empty — an empty array is a workspace with no bindings, while
|
|
2338
|
+
// an evaluation missing the array is a caller that never looked.
|
|
2339
|
+
const patternMemberships = patternInstances
|
|
2340
|
+
.flatMap(({ instance, pattern, bindings }) => [...bindings].map(([slot, member]) => ({
|
|
2341
|
+
member,
|
|
2342
|
+
slot,
|
|
2343
|
+
instance,
|
|
2344
|
+
pattern: pattern.kindIdentity,
|
|
2345
|
+
})))
|
|
2346
|
+
.sort((left, right) => left.member.localeCompare(right.member) ||
|
|
2347
|
+
left.pattern.localeCompare(right.pattern) ||
|
|
2348
|
+
left.instance.localeCompare(right.instance) ||
|
|
2349
|
+
left.slot.localeCompare(right.slot));
|
|
2335
2350
|
return {
|
|
2336
2351
|
ok: true,
|
|
2352
|
+
patternMemberships,
|
|
2337
2353
|
profileContext: {
|
|
2338
2354
|
conceptKindLineages: immutableMap([...conceptKindByIdentity]
|
|
2339
2355
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -2418,7 +2434,13 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2418
2434
|
}
|
|
2419
2435
|
export function compileWorkspace(sources) {
|
|
2420
2436
|
const result = compileWorkspaceResolved(parseSources(sources).parsed);
|
|
2421
|
-
return result.ok
|
|
2437
|
+
return result.ok
|
|
2438
|
+
? {
|
|
2439
|
+
ok: true,
|
|
2440
|
+
graph: result.graph,
|
|
2441
|
+
patternMemberships: result.patternMemberships,
|
|
2442
|
+
}
|
|
2443
|
+
: result;
|
|
2422
2444
|
}
|
|
2423
2445
|
export const compileWorkspaceWithProfileContext = (sources) => compileWorkspaceResolved(parseSources(sources).parsed);
|
|
2424
2446
|
/**
|
package/dist/design-command.js
CHANGED
|
@@ -246,7 +246,7 @@ export function runDesignCommand(options, cwd) {
|
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
|
-
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues);
|
|
249
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues, compilation.patternMemberships);
|
|
250
250
|
// Keyed by the QUALIFIED id, matching what the report now carries.
|
|
251
251
|
const askPlainById = new Map(composed.composed.catalogue.questions.flatMap((question) => question.askPlain === undefined
|
|
252
252
|
? []
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,9 @@ export { loadWorkspaceManifest, type ResolvedWorkspace, type WorkspaceManifest,
|
|
|
8
8
|
export { evaluateEvidence, evaluateEvidenceWorkspace, loadEvidence, type EvidenceDocument, type EvidenceEvaluationResult, type EvidenceLoadResult, type EvidenceLocator, type EvidenceObservation, type EvidenceObservedValue, type EvidenceReport, type EvidenceResult, type EvidenceWorkspaceEvaluationResult, } from './evidence.js';
|
|
9
9
|
export { constraintExpectsPredicate, reconcileEvidenceReports, type AssertedRelationship, type AttestationStaleness, type DeclaredSource, type EvidenceFinding, type ExpectationComparison, type ReconciliationFinding, type ReconciliationReport, type StaleAttestationFinding, type UnobservedExpectation, } from './reconciliation.js';
|
|
10
10
|
export { deriveAttestationStaleness } from './attestation-staleness.js';
|
|
11
|
+
export { deriveArtifactCoverage, type ArtifactCoverage, type CoverageScopePattern, } from './artifact-coverage.js';
|
|
11
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';
|
|
12
|
-
export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
13
|
+
export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, PatternMembership, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
13
14
|
export { canonicalProjection, evaluateProjection, explainProjection, loadProjection, renderProjectionMarkdown, type ConceptFacet, type ProjectionExclusion, } from './projection.js';
|
|
14
15
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, type AdapterMapping, type AdapterMappingLoadResult, type AdapterMappingValidationResult, type AdapterMappingsValidationResult, type AdapterSubjectMapping, } from './adapter-mapping.js';
|
|
15
16
|
export type { LifecycleStatus, ProjectionDefinition, ProjectionLoadResult, ProjectionResult, } from './projection.js';
|
|
@@ -18,4 +19,4 @@ export { applyOperations, landOperations, posixDirectoryOf, type ApplyInput, typ
|
|
|
18
19
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
19
20
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
20
21
|
export { deletionBlockers, describeDeletion, draftDeletion, type DeletionBlocker, } from './deletion-drafting.js';
|
|
21
|
-
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, 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';
|
|
22
|
+
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CataloguePatternMembership, 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
|
@@ -8,6 +8,7 @@ export { loadWorkspaceManifest, } from './workspace.js';
|
|
|
8
8
|
export { evaluateEvidence, evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
9
9
|
export { constraintExpectsPredicate, reconcileEvidenceReports, } from './reconciliation.js';
|
|
10
10
|
export { deriveAttestationStaleness } from './attestation-staleness.js';
|
|
11
|
+
export { deriveArtifactCoverage, } from './artifact-coverage.js';
|
|
11
12
|
export { buildRtm, renderRtmMarkdown, } from './rtm.js';
|
|
12
13
|
export { canonicalProjection, evaluateProjection, explainProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
13
14
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
|
|
@@ -89,6 +89,18 @@ export type CatalogueCondition = {
|
|
|
89
89
|
readonly condition: 'unchallenged-evidence';
|
|
90
90
|
} | {
|
|
91
91
|
readonly condition: 'has-any-subject';
|
|
92
|
+
} | {
|
|
93
|
+
/**
|
|
94
|
+
* The subject fills a slot of a pattern instance (ADR 0131). A GUARD,
|
|
95
|
+
* per the #334 split: it says a question applies here, and an
|
|
96
|
+
* ordinary condition beside it says what would answer it. Bare, it
|
|
97
|
+
* means bound into any slot of any instance; `patternKinds` narrows
|
|
98
|
+
* by the pattern's kind identity (never a document path, ADR 0129)
|
|
99
|
+
* and `slots` by part name.
|
|
100
|
+
*/
|
|
101
|
+
readonly condition: 'fills-pattern-slot';
|
|
102
|
+
readonly patternKinds?: readonly string[];
|
|
103
|
+
readonly slots?: readonly string[];
|
|
92
104
|
};
|
|
93
105
|
/**
|
|
94
106
|
* One observation from the workspace's evidence overlay, reduced to what
|
|
@@ -107,6 +119,21 @@ export interface CatalogueEvidenceObservation {
|
|
|
107
119
|
readonly result: 'confirmed' | 'contradicted' | 'unknown' | 'not-observed';
|
|
108
120
|
readonly searched?: readonly unknown[];
|
|
109
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* One slot of one pattern instance and the subject bound into it, as
|
|
124
|
+
* interrogation reads it. Shaped structurally rather than importing the
|
|
125
|
+
* compiler's {@link PatternMembership} for the same reason
|
|
126
|
+
* {@link CatalogueEvidenceObservation} is: the pure engine entry keeps
|
|
127
|
+
* owning its whole input surface, and a host passes
|
|
128
|
+
* `compilation.patternMemberships` without the compiler's types. Only
|
|
129
|
+
* `fills-pattern-slot` reads it (ADR 0131).
|
|
130
|
+
*/
|
|
131
|
+
export interface CataloguePatternMembership {
|
|
132
|
+
readonly member: string;
|
|
133
|
+
readonly slot: string;
|
|
134
|
+
readonly instance: string;
|
|
135
|
+
readonly pattern: string;
|
|
136
|
+
}
|
|
110
137
|
export interface CatalogueQuestion {
|
|
111
138
|
readonly id: string;
|
|
112
139
|
readonly wave: string;
|
|
@@ -210,7 +237,15 @@ export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: S
|
|
|
210
237
|
* the report. A fifth optional parameter rather than an options object,
|
|
211
238
|
* because this signature is published and a consumer already calls it.
|
|
212
239
|
*/
|
|
213
|
-
catalogues?: readonly string[]
|
|
240
|
+
catalogues?: readonly string[],
|
|
241
|
+
/**
|
|
242
|
+
* Pattern memberships from the compilation (ADR 0131) — pass
|
|
243
|
+
* `compilation.patternMemberships`, or `fills-pattern-slot` conditions
|
|
244
|
+
* never fire. A sixth optional parameter for the same reason
|
|
245
|
+
* `catalogues` is a fifth: this signature is published and a consumer
|
|
246
|
+
* already calls it.
|
|
247
|
+
*/
|
|
248
|
+
patternMemberships?: readonly CataloguePatternMembership[]): Omit<InterrogationReport, 'workspace'>;
|
|
214
249
|
export type CatalogueLoadResult = {
|
|
215
250
|
readonly ok: true;
|
|
216
251
|
readonly catalogue: QuestionCatalogue;
|
|
@@ -236,8 +236,20 @@ 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, evidence) => {
|
|
239
|
+
const conditionHolds = (index, condition, subjectId, profileContext, evidence, memberships) => {
|
|
240
240
|
switch (condition.condition) {
|
|
241
|
+
case 'fills-pattern-slot':
|
|
242
|
+
// Absent memberships stay quiet: the caller did not derive them, so
|
|
243
|
+
// participation is unknown, not absent — the same rule
|
|
244
|
+
// `unchallenged-evidence` applies to a missing overlay and
|
|
245
|
+
// `unconstrained-kind` to a missing profile context (ADR 0131).
|
|
246
|
+
return (memberships !== undefined &&
|
|
247
|
+
subjectId !== undefined &&
|
|
248
|
+
memberships.some((membership) => membership.member === subjectId &&
|
|
249
|
+
(condition.patternKinds === undefined ||
|
|
250
|
+
condition.patternKinds.includes(membership.pattern)) &&
|
|
251
|
+
(condition.slots === undefined ||
|
|
252
|
+
condition.slots.includes(membership.slot))));
|
|
241
253
|
case 'has-any-subject':
|
|
242
254
|
// The guard a late wave needs to say "only once the model has
|
|
243
255
|
// substance" (#334). An empty model is not an architecture at rest -
|
|
@@ -459,13 +471,21 @@ export function evaluateCatalogue(catalogue, graph, profileContext, evidence,
|
|
|
459
471
|
* the report. A fifth optional parameter rather than an options object,
|
|
460
472
|
* because this signature is published and a consumer already calls it.
|
|
461
473
|
*/
|
|
462
|
-
catalogues
|
|
474
|
+
catalogues,
|
|
475
|
+
/**
|
|
476
|
+
* Pattern memberships from the compilation (ADR 0131) — pass
|
|
477
|
+
* `compilation.patternMemberships`, or `fills-pattern-slot` conditions
|
|
478
|
+
* never fire. A sixth optional parameter for the same reason
|
|
479
|
+
* `catalogues` is a fifth: this signature is published and a consumer
|
|
480
|
+
* already calls it.
|
|
481
|
+
*/
|
|
482
|
+
patternMemberships) {
|
|
463
483
|
const index = indexGraph(graph);
|
|
464
484
|
let open = 0;
|
|
465
485
|
let openQuestions = 0;
|
|
466
486
|
const applicableQuestions = catalogue.questions.filter((question) => questionIsApplicable(question, graph.profiles));
|
|
467
487
|
const waveOpens = (wave) => wave.opensWhen === undefined ||
|
|
468
|
-
wave.opensWhen.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
|
|
488
|
+
wave.opensWhen.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence, patternMemberships));
|
|
469
489
|
const waves = catalogue.waves.map((wave) => ({
|
|
470
490
|
id: wave.id,
|
|
471
491
|
name: wave.name,
|
|
@@ -490,14 +510,14 @@ catalogues) {
|
|
|
490
510
|
...(question.since === undefined ? {} : { since: question.since }),
|
|
491
511
|
};
|
|
492
512
|
if (question.scope === 'workspace') {
|
|
493
|
-
const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence));
|
|
513
|
+
const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence, patternMemberships));
|
|
494
514
|
if (isOpen) {
|
|
495
515
|
open += 1;
|
|
496
516
|
openQuestions += 1;
|
|
497
517
|
}
|
|
498
518
|
return { ...base, open: isOpen };
|
|
499
519
|
}
|
|
500
|
-
const matches = selectSubjects(index, question.subjects, profileContext).filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence)));
|
|
520
|
+
const matches = selectSubjects(index, question.subjects, profileContext).filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence, patternMemberships)));
|
|
501
521
|
if (matches.length === 0) {
|
|
502
522
|
return { ...base, open: false };
|
|
503
523
|
}
|
|
@@ -556,7 +576,7 @@ const kindReferencesOf = (catalogue) => {
|
|
|
556
576
|
const fromCondition = (condition, path) => {
|
|
557
577
|
if (typeof condition !== 'object' || condition === null)
|
|
558
578
|
return;
|
|
559
|
-
for (const field of ['kinds', 'counterpartKinds']) {
|
|
579
|
+
for (const field of ['kinds', 'counterpartKinds', 'patternKinds']) {
|
|
560
580
|
const value = condition[field];
|
|
561
581
|
if (!Array.isArray(value))
|
|
562
582
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCompositionResult, type ComposedCatalogue, 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';
|
|
1
|
+
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCompositionResult, type ComposedCatalogue, type CatalogueCondition, type CatalogueEvidenceObservation, type CataloguePatternMembership, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
package/dist/reconciliation.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SemanticGraph } from './compiler.js';
|
|
2
2
|
import type { EvidenceLocator, EvidenceReport, EvidenceResult } from './evidence.js';
|
|
3
|
+
import type { ArtifactCoverage } from './artifact-coverage.js';
|
|
3
4
|
export interface AssertedRelationship {
|
|
4
5
|
readonly from: string;
|
|
5
6
|
readonly to: string;
|
|
@@ -98,11 +99,30 @@ export interface ReconciliationReport {
|
|
|
98
99
|
readonly unconfirmedAttestations?: number;
|
|
99
100
|
readonly expectationsCompared: number;
|
|
100
101
|
readonly expectationsWithoutObservation: number;
|
|
102
|
+
/**
|
|
103
|
+
* Files the declared coverage scope selected, and the ones no
|
|
104
|
+
* observation's `repo:` locator claims (ADR 0130). Both appear exactly
|
|
105
|
+
* when coverage was assessed, so a report without them is one that
|
|
106
|
+
* never looked, not one that found nothing.
|
|
107
|
+
*/
|
|
108
|
+
readonly artifactsInScope?: number;
|
|
109
|
+
readonly unclaimedArtifacts?: number;
|
|
101
110
|
};
|
|
102
111
|
readonly findings: readonly ReconciliationFinding[];
|
|
103
112
|
readonly unobservedSubjects?: readonly string[];
|
|
104
113
|
readonly unobservedExpectations?: readonly UnobservedExpectation[];
|
|
114
|
+
/**
|
|
115
|
+
* The coverage patterns as the manifest declared them, echoed so the
|
|
116
|
+
* report is honest about what it was asked to look at (ADR 0130).
|
|
117
|
+
*/
|
|
118
|
+
readonly coverageScope?: readonly string[];
|
|
119
|
+
/**
|
|
120
|
+
* In-scope artifacts no observation claims, sorted. Absence, never
|
|
121
|
+
* accusation: no finding is fabricated and `check --strict` never reads
|
|
122
|
+
* this (ADR 0130), the line ADR 0049 drew for unobserved subjects.
|
|
123
|
+
*/
|
|
124
|
+
readonly unclaimedArtifacts?: readonly string[];
|
|
105
125
|
readonly notes?: readonly string[];
|
|
106
126
|
}
|
|
107
127
|
export declare const constraintExpectsPredicate = "yarramate/constraint/expects";
|
|
108
|
-
export declare function reconcileEvidenceReports(workspace: string, reports: readonly EvidenceReport[], graph?: SemanticGraph, staleness?: AttestationStaleness): ReconciliationReport;
|
|
128
|
+
export declare function reconcileEvidenceReports(workspace: string, reports: readonly EvidenceReport[], graph?: SemanticGraph, staleness?: AttestationStaleness, coverage?: ArtifactCoverage): ReconciliationReport;
|
package/dist/reconciliation.js
CHANGED
|
@@ -149,6 +149,76 @@ const unobservedCurrentConcepts = (graph, reports) => {
|
|
|
149
149
|
.map(({ subject }) => subject)
|
|
150
150
|
.sort((left, right) => left.localeCompare(right));
|
|
151
151
|
};
|
|
152
|
+
// An observation claims an artifact when its locator is `repo:<path>`,
|
|
153
|
+
// read syntactically: any `#fragment` stripped, a directory claiming
|
|
154
|
+
// everything beneath it, any other scheme claiming nothing. Resolution
|
|
155
|
+
// and external validity stay with the provider (ADR 0130) — nothing here
|
|
156
|
+
// opens a file or checks one exists.
|
|
157
|
+
const claimedArtifactPaths = (reports) => {
|
|
158
|
+
const claimed = new Set();
|
|
159
|
+
for (const report of reports) {
|
|
160
|
+
for (const observation of report.observations) {
|
|
161
|
+
const uri = observation.evidence.uri;
|
|
162
|
+
if (!uri.startsWith('repo:'))
|
|
163
|
+
continue;
|
|
164
|
+
const located = uri.slice('repo:'.length);
|
|
165
|
+
const fragment = located.indexOf('#');
|
|
166
|
+
// Trailing slashes are stripped with a loop, not a regex: an
|
|
167
|
+
// anchored /\/+$/ is polynomial on a locator of many slashes, and
|
|
168
|
+
// locators are library input.
|
|
169
|
+
let path = fragment === -1 ? located : located.slice(0, fragment);
|
|
170
|
+
while (path.endsWith('/'))
|
|
171
|
+
path = path.slice(0, -1);
|
|
172
|
+
if (path.length > 0)
|
|
173
|
+
claimed.add(path);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return claimed;
|
|
177
|
+
};
|
|
178
|
+
const isClaimed = (claimed, artifact) => {
|
|
179
|
+
if (claimed.has(artifact))
|
|
180
|
+
return true;
|
|
181
|
+
for (const path of claimed) {
|
|
182
|
+
if (artifact.startsWith(`${path}/`))
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
};
|
|
187
|
+
const assessArtifactCoverage = (coverage, reports) => {
|
|
188
|
+
// An absent argument is a caller that never looked (the strict gate);
|
|
189
|
+
// an unassessed derivation is a reconcile that looked and could not,
|
|
190
|
+
// and says why (ADR 0130, the ADR 0074 parallel).
|
|
191
|
+
if (coverage === undefined)
|
|
192
|
+
return { notes: [] };
|
|
193
|
+
if (!coverage.assessed) {
|
|
194
|
+
return {
|
|
195
|
+
notes: [`Artifact coverage was not assessed: ${coverage.reason}.`],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const notes = [];
|
|
199
|
+
if (coverage.scope.length === 0) {
|
|
200
|
+
notes.push('The workspace manifest declares an empty coverage scope, so no artifacts were assessed.');
|
|
201
|
+
}
|
|
202
|
+
// A dead glob is indistinguishable from a typo, and silently
|
|
203
|
+
// contributing nothing is how a mistyped pattern would report full
|
|
204
|
+
// coverage. A note, not a refusal: a coverage scope is a lens, not
|
|
205
|
+
// load-bearing input (ADR 0130).
|
|
206
|
+
for (const { pattern, artifacts } of coverage.scope) {
|
|
207
|
+
if (artifacts.length === 0) {
|
|
208
|
+
notes.push(`Coverage pattern "${pattern}" matched no artifacts.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const artifacts = [
|
|
212
|
+
...new Set(coverage.scope.flatMap(({ artifacts }) => artifacts)),
|
|
213
|
+
].sort((left, right) => left.localeCompare(right));
|
|
214
|
+
const claimed = claimedArtifactPaths(reports);
|
|
215
|
+
return {
|
|
216
|
+
scope: coverage.scope.map(({ pattern }) => pattern),
|
|
217
|
+
artifacts: artifacts.length,
|
|
218
|
+
unclaimed: artifacts.filter((artifact) => !isClaimed(claimed, artifact)),
|
|
219
|
+
notes,
|
|
220
|
+
};
|
|
221
|
+
};
|
|
152
222
|
// A judgment a machine transcribed is not the act the authority
|
|
153
223
|
// performed. The recorder is in the model, so the difference is
|
|
154
224
|
// derivable here: an authority who wrote the record in their own hand
|
|
@@ -186,11 +256,12 @@ const unconfirmedAttestations = (graph) => {
|
|
|
186
256
|
];
|
|
187
257
|
});
|
|
188
258
|
};
|
|
189
|
-
export function reconcileEvidenceReports(workspace, reports, graph, staleness) {
|
|
259
|
+
export function reconcileEvidenceReports(workspace, reports, graph, staleness, coverage) {
|
|
190
260
|
const assertedByClaim = assertedRelationshipsByClaim(graph);
|
|
191
261
|
const unobservedSubjects = unobservedCurrentConcepts(graph, reports);
|
|
192
262
|
const expectations = compareExpectations(graph, reports);
|
|
193
263
|
const unconfirmed = unconfirmedAttestations(graph);
|
|
264
|
+
const assessedCoverage = assessArtifactCoverage(coverage, reports);
|
|
194
265
|
const summary = {
|
|
195
266
|
evidenceDocuments: reports.length,
|
|
196
267
|
observations: 0,
|
|
@@ -215,6 +286,15 @@ export function reconcileEvidenceReports(workspace, reports, graph, staleness) {
|
|
|
215
286
|
: { unconfirmedAttestations: unconfirmed.length }),
|
|
216
287
|
expectationsCompared: expectations.compared,
|
|
217
288
|
expectationsWithoutObservation: expectations.unobserved.length,
|
|
289
|
+
// Coverage counters appear exactly when the scope was assessed
|
|
290
|
+
// (ADR 0130): a report without them never looked; a report carrying
|
|
291
|
+
// zero looked and found everything claimed.
|
|
292
|
+
...(assessedCoverage.artifacts === undefined
|
|
293
|
+
? {}
|
|
294
|
+
: {
|
|
295
|
+
artifactsInScope: assessedCoverage.artifacts,
|
|
296
|
+
unclaimedArtifacts: assessedCoverage.unclaimed?.length ?? 0,
|
|
297
|
+
}),
|
|
218
298
|
};
|
|
219
299
|
const findings = [
|
|
220
300
|
...(staleness?.findings ?? []),
|
|
@@ -274,7 +354,11 @@ export function reconcileEvidenceReports(workspace, reports, graph, staleness) {
|
|
|
274
354
|
(expectationOf(left)?.key ?? '').localeCompare(expectationOf(right)?.key ?? '') ||
|
|
275
355
|
(expectationOf(left)?.observed ?? '').localeCompare(expectationOf(right)?.observed ?? ''));
|
|
276
356
|
summary.findings = findings.length;
|
|
277
|
-
const notes = [
|
|
357
|
+
const notes = [
|
|
358
|
+
...(staleness?.notes ?? []),
|
|
359
|
+
...absenceNotes,
|
|
360
|
+
...assessedCoverage.notes,
|
|
361
|
+
];
|
|
278
362
|
return {
|
|
279
363
|
format: 'yarramate/reconciliation-report/v1',
|
|
280
364
|
workspace,
|
|
@@ -284,6 +368,13 @@ export function reconcileEvidenceReports(workspace, reports, graph, staleness) {
|
|
|
284
368
|
...(expectations.unobserved.length === 0
|
|
285
369
|
? {}
|
|
286
370
|
: { unobservedExpectations: expectations.unobserved }),
|
|
371
|
+
...(assessedCoverage.scope === undefined
|
|
372
|
+
? {}
|
|
373
|
+
: { coverageScope: assessedCoverage.scope }),
|
|
374
|
+
...(assessedCoverage.unclaimed === undefined ||
|
|
375
|
+
assessedCoverage.unclaimed.length === 0
|
|
376
|
+
? {}
|
|
377
|
+
: { unclaimedArtifacts: assessedCoverage.unclaimed }),
|
|
287
378
|
...(notes.length === 0 ? {} : { notes }),
|
|
288
379
|
};
|
|
289
380
|
}
|