yarramate 1.17.0 → 1.19.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 +2 -0
- package/dist/adapters/visual/workspace-model.d.ts +5 -1
- package/dist/adapters/visual/workspace-model.js +1 -1
- package/dist/apply-command.js +72 -0
- package/dist/ask-command.js +4 -3
- package/dist/compiler.d.ts +28 -0
- package/dist/compiler.js +162 -31
- package/dist/design-command.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/interrogate-command.d.ts +106 -1
- package/dist/interrogate-command.js +101 -22
- package/dist/interrogation-entry.d.ts +1 -1
- package/dist/interrogation-entry.js +1 -1
- package/dist/operations.d.ts +14 -0
- package/dist/schema-validators-operations.generated.js +95 -31
- package/dist/schema-validators.generated.js +244 -25
- package/dist/visual-app-lib/editor.js +25765 -25369
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +5 -1
- package/dist/visual-app-lib/types/compiler.d.ts +28 -0
- package/dist/visual-app-lib/types/interrogate-command.d.ts +106 -1
- package/dist/visual-app-lib/types/operations.d.ts +14 -0
- package/docs/INTERROGATION.md +19 -0
- package/docs/NATIVE-DOCUMENT.md +18 -4
- package/package.json +1 -1
- package/schema/yarramate-ask-result.schema.json +8 -0
- package/schema/yarramate-interrogation-report.schema.json +30 -0
- package/schema/yarramate-operations.schema.json +12 -0
- package/schema/yarramate-pattern.schema.json +4 -0
- package/schema/yarramate-question-catalogue.schema.json +30 -0
|
@@ -624,6 +624,8 @@ export const startVisualServer = async (options) => {
|
|
|
624
624
|
// Threaded whole (ADR 0131): a narrow copy here is how a slot
|
|
625
625
|
// question would silently never fire in the embedded pane.
|
|
626
626
|
patternMemberships: compiled.patternMemberships,
|
|
627
|
+
// The vacant half, on the same rule (#447).
|
|
628
|
+
patternVacancies: compiled.patternVacancies,
|
|
627
629
|
};
|
|
628
630
|
const workspaceModel = renderedWorkspaceOf(compiledWorkspace, views, {
|
|
629
631
|
authority: rendered.authority,
|
|
@@ -4,7 +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
|
+
import { type CataloguePatternMembership, type CataloguePatternVacancy } from "../../interrogate-command.js";
|
|
8
8
|
import type { VisualKindOption, VisualViewSummary } from "./protocol-contract.js";
|
|
9
9
|
import type { VisualInterrogationOverlay, VisualRenderedModel } from "./wire.js";
|
|
10
10
|
/**
|
|
@@ -60,6 +60,8 @@ export declare const interrogationOverlayOf: (compiled: {
|
|
|
60
60
|
readonly profileContext: ResolvedProfileContext;
|
|
61
61
|
/** From the compilation (ADR 0131); absent, slot questions stay quiet. */
|
|
62
62
|
readonly patternMemberships?: readonly CataloguePatternMembership[];
|
|
63
|
+
/** From the compilation (#447); absent, `missing-part` stays quiet. */
|
|
64
|
+
readonly patternVacancies?: readonly CataloguePatternVacancy[];
|
|
63
65
|
},
|
|
64
66
|
/**
|
|
65
67
|
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
@@ -94,6 +96,8 @@ export declare const renderedWorkspaceOf: (compiled: {
|
|
|
94
96
|
readonly graph: SemanticGraph;
|
|
95
97
|
readonly profileContext: ResolvedProfileContext;
|
|
96
98
|
readonly patternMemberships?: readonly CataloguePatternMembership[];
|
|
99
|
+
/** Threaded on to the overlay (#447); absent, `missing-part` stays quiet. */
|
|
100
|
+
readonly patternVacancies?: readonly CataloguePatternVacancy[];
|
|
97
101
|
}, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary" | "interrogation">, catalogue?: {
|
|
98
102
|
readonly path: string;
|
|
99
103
|
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, compiled.patternMemberships);
|
|
67
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compiled.graph, compiled.profileContext, undefined, composed.composed.catalogues, compiled.patternMemberships, compiled.patternVacancies);
|
|
68
68
|
const dismissedEverywhere = new Set(dismissed
|
|
69
69
|
.filter(({ subject }) => subject === undefined)
|
|
70
70
|
.map(({ questionId }) => questionId));
|
package/dist/apply-command.js
CHANGED
|
@@ -27,6 +27,10 @@ import { validateOperations } from './schema-validation.js';
|
|
|
27
27
|
// asserted — it never silently shrinks anything.
|
|
28
28
|
const SCALAR_CONCEPT_FIELDS = ['kind', 'name', 'description', 'status', 'owner'];
|
|
29
29
|
const LIST_CONCEPT_FIELDS = ['aka', 'constraints', 'references', 'presentIn', 'attestations', 'distinctFrom', 'supersedes'];
|
|
30
|
+
// The third category (#448). `parts` is the first MAP-valued concept field:
|
|
31
|
+
// it neither replaces like a scalar nor appends like a list, it merges by
|
|
32
|
+
// slot. See `mergeMapField`.
|
|
33
|
+
const MAP_CONCEPT_FIELDS = ['parts'];
|
|
30
34
|
const SCALAR_RELATIONSHIP_FIELDS = ['kind', 'from', 'to', 'name', 'description', 'status', 'mode', 'content'];
|
|
31
35
|
const LIST_RELATIONSHIP_FIELDS = ['references', 'presentIn'];
|
|
32
36
|
// An overlay entry's address is the pair (target, key); everything else it
|
|
@@ -233,6 +237,66 @@ const appendListField = (source, map, key, additions) => {
|
|
|
233
237
|
}
|
|
234
238
|
return spliceValue(source, start, valueEnd, `\n${sequenceEntries(merged, indent + 2)}`);
|
|
235
239
|
};
|
|
240
|
+
// Merge a MAP-valued field by key (#448). A named slot rebinds, an unnamed one
|
|
241
|
+
// is untouched: ADR 0062's convention, where a write enriches what is there and
|
|
242
|
+
// never silently shrinks it. Replacing the whole map would unbind slots the
|
|
243
|
+
// operation never mentioned, which is exactly the silent shrinking that rule
|
|
244
|
+
// forbids.
|
|
245
|
+
//
|
|
246
|
+
// The per-slot work is `setScalarField` against the NESTED map, because
|
|
247
|
+
// inserting or replacing one key of a mapping is what that already does. The
|
|
248
|
+
// source is re-parsed between slots for the same reason the caller re-parses
|
|
249
|
+
// between fields: every splice moves the offsets after it.
|
|
250
|
+
const mergeMapField = (source, locateMap, key, additions) => {
|
|
251
|
+
const entries = Object.entries(additions);
|
|
252
|
+
if (entries.length === 0)
|
|
253
|
+
return source;
|
|
254
|
+
const map = locateMap(source);
|
|
255
|
+
if (map === undefined)
|
|
256
|
+
return source;
|
|
257
|
+
if (map.flow) {
|
|
258
|
+
return rewriteFlowItem(source, map, (fields) => ({
|
|
259
|
+
...fields,
|
|
260
|
+
[key]: {
|
|
261
|
+
...(fields[key] ?? {}),
|
|
262
|
+
...additions,
|
|
263
|
+
},
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
const existing = nestedMap(map, key);
|
|
267
|
+
if (existing === undefined) {
|
|
268
|
+
const pair = pairFor(map, key);
|
|
269
|
+
const indent = fieldIndentOf(source, map);
|
|
270
|
+
const rendered = entries
|
|
271
|
+
.map(([slot, value]) => `${' '.repeat(indent + 2)}${slot}: ${valueText(value)}`)
|
|
272
|
+
.join('\n');
|
|
273
|
+
// A `parts` that exists but is not a block mapping (flow, or empty) is
|
|
274
|
+
// replaced wholesale rather than merged into: there is nothing to preserve
|
|
275
|
+
// that the entries do not already carry.
|
|
276
|
+
if (pair !== undefined) {
|
|
277
|
+
const held = isMap(pair.value)
|
|
278
|
+
? pair.value.toJSON()
|
|
279
|
+
: {};
|
|
280
|
+
const merged = { ...held, ...additions };
|
|
281
|
+
const [start, valueEnd] = nodeRange(pair.value);
|
|
282
|
+
return spliceValue(source, start, valueEnd, `\n${Object.entries(merged)
|
|
283
|
+
.map(([slot, value]) => `${' '.repeat(indent + 2)}${slot}: ${valueText(value)}`)
|
|
284
|
+
.join('\n')}`);
|
|
285
|
+
}
|
|
286
|
+
return insertBlock(source, itemFieldInsertAt(source, map), `${' '.repeat(indent)}${key}:\n${rendered}\n`);
|
|
287
|
+
}
|
|
288
|
+
let updated = source;
|
|
289
|
+
for (const [slot, value] of entries) {
|
|
290
|
+
const host = locateMap(updated);
|
|
291
|
+
if (host === undefined)
|
|
292
|
+
break;
|
|
293
|
+
const target = nestedMap(host, key);
|
|
294
|
+
if (target === undefined)
|
|
295
|
+
break;
|
|
296
|
+
updated = setScalarField(updated, target, slot, value);
|
|
297
|
+
}
|
|
298
|
+
return updated;
|
|
299
|
+
};
|
|
236
300
|
// Retraction (#115): delete the field's whole entry, from the start of its
|
|
237
301
|
// key line through the end of its value's last line. A flow item is
|
|
238
302
|
// rewritten instead — line-based deletion there would take the whole item
|
|
@@ -602,6 +666,14 @@ export const applyOperations = (input) => {
|
|
|
602
666
|
continue;
|
|
603
667
|
source = appendListField(source, itemMap(source, collection, id).map, key, additions);
|
|
604
668
|
}
|
|
669
|
+
if (operation.op === 'update-concept') {
|
|
670
|
+
for (const key of MAP_CONCEPT_FIELDS) {
|
|
671
|
+
const additions = payload[key];
|
|
672
|
+
if (additions === undefined)
|
|
673
|
+
continue;
|
|
674
|
+
source = mergeMapField(source, (current) => itemMap(current, collection, id)?.map, key, additions);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
605
677
|
for (const key of removals) {
|
|
606
678
|
const removed = removeField(source, itemMap(source, collection, id).map, key);
|
|
607
679
|
if (removed === undefined) {
|
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, compilation.patternMemberships);
|
|
478
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues, compilation.patternMemberships, compilation.patternVacancies);
|
|
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, compilation.patternMemberships),
|
|
722
|
+
...evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues, compilation.patternMemberships, compilation.patternVacancies),
|
|
723
723
|
workspace: workspace.id,
|
|
724
724
|
};
|
|
725
725
|
// Field by field, to fix key ORDER in the emitted JSON. Every optional
|
|
@@ -735,6 +735,7 @@ export function runAskCommand(options, cwd) {
|
|
|
735
735
|
? {}
|
|
736
736
|
: { catalogues: report.catalogues }),
|
|
737
737
|
semantics: report.semantics,
|
|
738
|
+
inputs: report.inputs,
|
|
738
739
|
summary: report.summary,
|
|
739
740
|
waves: report.waves,
|
|
740
741
|
};
|
|
@@ -1021,7 +1022,7 @@ export function runAskCommand(options, cwd) {
|
|
|
1021
1022
|
return failed(loaded.diagnostics);
|
|
1022
1023
|
evidenceDocuments.push(loaded.evidence);
|
|
1023
1024
|
}
|
|
1024
|
-
const report = evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues, compilation.patternMemberships);
|
|
1025
|
+
const report = evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues, compilation.patternMemberships, compilation.patternVacancies);
|
|
1025
1026
|
const openQuestions = [];
|
|
1026
1027
|
for (const wave of report.waves) {
|
|
1027
1028
|
for (const question of wave.questions) {
|
package/dist/compiler.d.ts
CHANGED
|
@@ -109,6 +109,31 @@ export interface PatternMembership {
|
|
|
109
109
|
readonly instance: string;
|
|
110
110
|
readonly pattern: string;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* One slot of one pattern instance that nothing is bound into (#447): the
|
|
114
|
+
* shape of a question a pattern already knows to ask. The mirror of
|
|
115
|
+
* {@link PatternMembership}, and it has to be a second array rather than a
|
|
116
|
+
* widening of that one, because a vacancy has no `member` and making that
|
|
117
|
+
* field optional would break every reader.
|
|
118
|
+
*
|
|
119
|
+
* `required` is here because a vacancy on a SUCCESSFUL compile can be a
|
|
120
|
+
* required slot after all. The first draft of this shape left the field out,
|
|
121
|
+
* reasoning that a required slot left unbound is `YM416` and so never reaches
|
|
122
|
+
* a result. That is true only of an instance that declares `parts`: a concept
|
|
123
|
+
* whose kind has a pattern but which declares no parts at all is not a
|
|
124
|
+
* `PatternInstance`, never reaches YM416, and compiles clean with every slot
|
|
125
|
+
* vacant. So the flag is load-bearing rather than permanently `false`, and it
|
|
126
|
+
* is the difference between "you have not decided this yet" and "this model
|
|
127
|
+
* does not stand up without it".
|
|
128
|
+
*/
|
|
129
|
+
export interface PatternVacancy {
|
|
130
|
+
readonly instance: string;
|
|
131
|
+
readonly pattern: string;
|
|
132
|
+
readonly slot: string;
|
|
133
|
+
readonly slotKind: string;
|
|
134
|
+
/** The pattern declares this part `required` (ADR 0123). */
|
|
135
|
+
readonly required: boolean;
|
|
136
|
+
}
|
|
112
137
|
export type CompilationResult = {
|
|
113
138
|
readonly ok: true;
|
|
114
139
|
readonly graph: SemanticGraph;
|
|
@@ -119,6 +144,8 @@ export type CompilationResult = {
|
|
|
119
144
|
* to `evaluateCatalogue`, or `fills-pattern-slot` never fires.
|
|
120
145
|
*/
|
|
121
146
|
readonly patternMemberships?: readonly PatternMembership[];
|
|
147
|
+
/** Same rule as `patternMemberships`, for the same reason (#447). */
|
|
148
|
+
readonly patternVacancies?: readonly PatternVacancy[];
|
|
122
149
|
} | {
|
|
123
150
|
readonly ok: false;
|
|
124
151
|
readonly diagnostics: readonly Diagnostic[];
|
|
@@ -128,6 +155,7 @@ export type ContextualCompilationResult = {
|
|
|
128
155
|
readonly graph: SemanticGraph;
|
|
129
156
|
readonly profileContext: ResolvedProfileContext;
|
|
130
157
|
readonly patternMemberships?: readonly PatternMembership[];
|
|
158
|
+
readonly patternVacancies?: readonly PatternVacancy[];
|
|
131
159
|
} | {
|
|
132
160
|
readonly ok: false;
|
|
133
161
|
readonly diagnostics: readonly Diagnostic[];
|
package/dist/compiler.js
CHANGED
|
@@ -689,6 +689,7 @@ function compileWorkspaceResolved(parsed) {
|
|
|
689
689
|
name: slot,
|
|
690
690
|
kindIdentity: kind.identity,
|
|
691
691
|
required: part.required === true,
|
|
692
|
+
kindMatching: part.kindMatching === 'descendants' ? 'descendants' : 'exact',
|
|
692
693
|
});
|
|
693
694
|
}
|
|
694
695
|
if (!slotsOk)
|
|
@@ -846,6 +847,18 @@ function compileWorkspaceResolved(parsed) {
|
|
|
846
847
|
return diagnosticFailure(patternDiagnostics);
|
|
847
848
|
}
|
|
848
849
|
const patternInstances = [];
|
|
850
|
+
/**
|
|
851
|
+
* Concepts whose kind HAS a pattern but which declare no `parts` at all —
|
|
852
|
+
* the greenfield instance ADR 0123 named and left to a later phase, and the
|
|
853
|
+
* one an interview has the most to ask (#447).
|
|
854
|
+
*
|
|
855
|
+
* Deliberately NOT `patternInstances`. That list drives YM416, wiring
|
|
856
|
+
* expansion and membership, and adding these to it would start refusing
|
|
857
|
+
* workspaces that compile today. Instance-hood for the purpose of BEING
|
|
858
|
+
* ASKED is a wider question than instance-hood for the purpose of being
|
|
859
|
+
* expanded, so it gets its own list rather than a widened one.
|
|
860
|
+
*/
|
|
861
|
+
const unbegunInstances = [];
|
|
849
862
|
const documents = documentInputs.map(({ input, entry, fresh }) => {
|
|
850
863
|
// Schema-checked by `parseWorkspaceSource`; the faults it found are the
|
|
851
864
|
// `schemaDiagnostics` returned below, and they gate every later phase.
|
|
@@ -1562,9 +1575,17 @@ function compileWorkspaceResolved(parsed) {
|
|
|
1562
1575
|
// A pattern instance is only COLLECTED here. What its slots bind may be
|
|
1563
1576
|
// declared in any document, so the bindings cannot be checked until
|
|
1564
1577
|
// every document has been read (#268, ADR 0123).
|
|
1578
|
+
const conceptKindIdentity = selectedProfile.conceptKinds.get(concept.kind)?.identity ?? concept.kind;
|
|
1579
|
+
if (concept.parts === undefined) {
|
|
1580
|
+
// Nothing to bind and nothing to expand, but the kind still promises a
|
|
1581
|
+
// shape, so the interview can still ask about it (#447).
|
|
1582
|
+
const pattern = patternsByKind.get(conceptKindIdentity);
|
|
1583
|
+
if (pattern !== undefined) {
|
|
1584
|
+
unbegunInstances.push({ instance: subject, pattern });
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1565
1587
|
if (concept.parts !== undefined) {
|
|
1566
|
-
const kindIdentity =
|
|
1567
|
-
concept.kind;
|
|
1588
|
+
const kindIdentity = conceptKindIdentity;
|
|
1568
1589
|
const pattern = patternsByKind.get(kindIdentity);
|
|
1569
1590
|
if (pattern === undefined) {
|
|
1570
1591
|
const where = location(['concepts', index, 'parts'], `/concepts/${index}/parts`);
|
|
@@ -2067,6 +2088,13 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2067
2088
|
else
|
|
2068
2089
|
group.push(entry);
|
|
2069
2090
|
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Every wire that wants to mint, keyed by the TRIPLE it names (#460).
|
|
2093
|
+
* Collected across all instances before any is minted, because two wires
|
|
2094
|
+
* belonging to different instances can name one triple and only one claim
|
|
2095
|
+
* may survive.
|
|
2096
|
+
*/
|
|
2097
|
+
const wireCandidates = new Map();
|
|
2070
2098
|
for (const { instance, pattern, bindings, sourceOf } of patternInstances) {
|
|
2071
2099
|
const boundTo = new Map();
|
|
2072
2100
|
for (const [slot, target] of bindings) {
|
|
@@ -2103,13 +2131,27 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2103
2131
|
}
|
|
2104
2132
|
boundTo.set(target, slot);
|
|
2105
2133
|
const actual = kindOfSubject.get(target);
|
|
2106
|
-
|
|
2134
|
+
// `descendants` admits any kind whose lineage includes the slot kind
|
|
2135
|
+
// (#449), which is what the word already means on catalogue selectors
|
|
2136
|
+
// and on `missing-relationship`. It fails safe: minted wiring is
|
|
2137
|
+
// checked against the relationship table using the ACTUAL bound
|
|
2138
|
+
// subjects' kinds, so a descendant that is not a legal endpoint is
|
|
2139
|
+
// still refused by the ordinary relationship check rather than
|
|
2140
|
+
// slipping through on the pattern's authority.
|
|
2141
|
+
const admitted = actual === slotShape.kindIdentity ||
|
|
2142
|
+
(slotShape.kindMatching === 'descendants' &&
|
|
2143
|
+
actual !== undefined &&
|
|
2144
|
+
(conceptKindByIdentity.get(actual)?.lineage ?? []).includes(slotShape.kindIdentity));
|
|
2145
|
+
if (!admitted) {
|
|
2107
2146
|
diagnostics.push({
|
|
2108
2147
|
severity: 'error',
|
|
2109
2148
|
code: 'YM417',
|
|
2110
2149
|
message: `Part "${slot}" of "${instance}" binds "${target}", which is ` +
|
|
2111
2150
|
`"${actual ?? 'not a concept'}"; the pattern declares this part ` +
|
|
2112
|
-
`"${slotShape.kindIdentity}"
|
|
2151
|
+
`"${slotShape.kindIdentity}"` +
|
|
2152
|
+
(slotShape.kindMatching === 'descendants'
|
|
2153
|
+
? ' or a kind descending from it'
|
|
2154
|
+
: ''),
|
|
2113
2155
|
path: where.path,
|
|
2114
2156
|
pointer: where.pointer,
|
|
2115
2157
|
line: where.line,
|
|
@@ -2173,36 +2215,89 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2173
2215
|
}
|
|
2174
2216
|
if (satisfied)
|
|
2175
2217
|
continue;
|
|
2176
|
-
|
|
2218
|
+
// Collected rather than minted, because whether this wire mints
|
|
2219
|
+
// depends on wires belonging to OTHER instances (#460). Two wires can
|
|
2220
|
+
// name one triple, and before this they both minted: two relationship
|
|
2221
|
+
// claims for one relationship.
|
|
2222
|
+
const key = `${from}\u0000${wire.kindIdentity}\u0000${to}`;
|
|
2223
|
+
const candidate = {
|
|
2177
2224
|
instance,
|
|
2178
|
-
|
|
2179
|
-
wire
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2225
|
+
pattern,
|
|
2226
|
+
wire,
|
|
2227
|
+
from,
|
|
2228
|
+
to,
|
|
2229
|
+
where,
|
|
2230
|
+
owner: wire.from === 'self',
|
|
2231
|
+
wireId: [
|
|
2232
|
+
instance,
|
|
2233
|
+
...(wire.from === 'self' ? [] : [wire.from]),
|
|
2234
|
+
wire.coreKind,
|
|
2235
|
+
wire.to,
|
|
2236
|
+
].join('-'),
|
|
2237
|
+
};
|
|
2238
|
+
const group = wireCandidates.get(key);
|
|
2239
|
+
if (group === undefined)
|
|
2240
|
+
wireCandidates.set(key, [candidate]);
|
|
2241
|
+
else
|
|
2242
|
+
group.push(candidate);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
// One claim per triple, whichever wires named it (#460, ADR 0141).
|
|
2246
|
+
//
|
|
2247
|
+
// OWNERSHIP decides the id, not walk order. A wire whose `from` is `self`
|
|
2248
|
+
// owns the edge, because the edge leaves that instance; a wire whose
|
|
2249
|
+
// `from` is a slot is a guest naming somebody else's edge, and defers. So
|
|
2250
|
+
// an edge with an owner keeps the ADR 0123 id it has always had, and
|
|
2251
|
+
// nothing moves for a workspace that compiles today.
|
|
2252
|
+
//
|
|
2253
|
+
// Where every wire is a guest the owner's wire is absent — typically its
|
|
2254
|
+
// instance is greenfield, and an unbound slot wires nothing — so there is
|
|
2255
|
+
// no ADR 0123 id to prefer and the id comes from the TRIPLE instead. That
|
|
2256
|
+
// is stable under adding or removing further guests, which sorting a
|
|
2257
|
+
// winner out of the guests would not be.
|
|
2258
|
+
for (const [, group] of [...wireCandidates].sort(([left], [right]) => left.localeCompare(right))) {
|
|
2259
|
+
const owners = group
|
|
2260
|
+
.filter(({ owner }) => owner)
|
|
2261
|
+
// Two owners on one triple cannot happen: `self` differs per instance,
|
|
2262
|
+
// and within one instance YM315 already refuses two slots naming one
|
|
2263
|
+
// subject. Sorted anyway rather than trusting that from a distance.
|
|
2264
|
+
.sort((left, right) => left.wireId.localeCompare(right.wireId));
|
|
2265
|
+
const winner = owners[0] ?? group[0];
|
|
2266
|
+
if (winner === undefined)
|
|
2267
|
+
continue;
|
|
2268
|
+
// A group of ONE is not a collision and keeps its ADR 0123 id, always.
|
|
2269
|
+
// This is the case that matters most, because a wire between two SLOTS
|
|
2270
|
+
// of one pattern (`component --composition--> interface`) has no `self`
|
|
2271
|
+
// endpoint and so no owner, yet nothing is competing with it. Deriving
|
|
2272
|
+
// its id from the triple would rename every such edge in every
|
|
2273
|
+
// workspace, which is exactly the migration ownership exists to avoid;
|
|
2274
|
+
// ADR 0123's own worked example is one of them.
|
|
2275
|
+
const wireId = group.length === 1 || owners.length > 0
|
|
2276
|
+
? winner.wireId
|
|
2277
|
+
: [winner.from, winner.wire.coreKind, winner.to].join('-');
|
|
2278
|
+
if (declaredIds.has(wireId)) {
|
|
2279
|
+
diagnostics.push({
|
|
2280
|
+
severity: 'error',
|
|
2281
|
+
code: 'YM420',
|
|
2282
|
+
message: `The pattern for "${winner.pattern.kindIdentity}" derives the wiring id ` +
|
|
2283
|
+
`"${wireId}" for "${winner.instance}", which is already a declared subject`,
|
|
2284
|
+
path: winner.where.path,
|
|
2285
|
+
pointer: winner.where.pointer,
|
|
2286
|
+
line: winner.where.line,
|
|
2287
|
+
column: winner.where.column,
|
|
2204
2288
|
});
|
|
2289
|
+
continue;
|
|
2205
2290
|
}
|
|
2291
|
+
declaredIds.add(wireId);
|
|
2292
|
+
subjects.push({ id: wireId, type: 'relationship' });
|
|
2293
|
+
claims.push({
|
|
2294
|
+
id: wireId,
|
|
2295
|
+
subject: winner.from,
|
|
2296
|
+
predicate: winner.wire.kindIdentity,
|
|
2297
|
+
object: { ref: winner.to },
|
|
2298
|
+
origin: 'declared',
|
|
2299
|
+
source: winner.where,
|
|
2300
|
+
});
|
|
2206
2301
|
}
|
|
2207
2302
|
}
|
|
2208
2303
|
// ---- macro edges through ports (#268 phase 2, ADR 0124) ------------------
|
|
@@ -2349,9 +2444,44 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2349
2444
|
left.pattern.localeCompare(right.pattern) ||
|
|
2350
2445
|
left.instance.localeCompare(right.instance) ||
|
|
2351
2446
|
left.slot.localeCompare(right.slot));
|
|
2447
|
+
// The mirror (#447): one entry per slot nothing was bound into. Always
|
|
2448
|
+
// emitted, possibly empty — an empty array is a workspace whose instances
|
|
2449
|
+
// are fully bound, while a missing array is a caller that never looked.
|
|
2450
|
+
//
|
|
2451
|
+
// Derived from BOTH lists, and that is the substance of the thing. An
|
|
2452
|
+
// instance that declares some parts is asked about the rest; an instance
|
|
2453
|
+
// that declares none is asked about all of them, because a template nobody
|
|
2454
|
+
// has begun filling in is the one with the most blanks, not the one with
|
|
2455
|
+
// none. Reading only `patternInstances` here would have reported `[]` for
|
|
2456
|
+
// it, and `[]` means "fully bound".
|
|
2457
|
+
const patternVacancies = [
|
|
2458
|
+
...patternInstances.map(({ instance, pattern, bindings }) => ({
|
|
2459
|
+
instance,
|
|
2460
|
+
pattern,
|
|
2461
|
+
bound: bindings,
|
|
2462
|
+
})),
|
|
2463
|
+
...unbegunInstances.map(({ instance, pattern }) => ({
|
|
2464
|
+
instance,
|
|
2465
|
+
pattern,
|
|
2466
|
+
bound: new Map(),
|
|
2467
|
+
})),
|
|
2468
|
+
]
|
|
2469
|
+
.flatMap(({ instance, pattern, bound }) => [...pattern.slots.values()]
|
|
2470
|
+
.filter((slot) => !bound.has(slot.name))
|
|
2471
|
+
.map((slot) => ({
|
|
2472
|
+
instance,
|
|
2473
|
+
pattern: pattern.kindIdentity,
|
|
2474
|
+
slot: slot.name,
|
|
2475
|
+
slotKind: slot.kindIdentity,
|
|
2476
|
+
required: slot.required === true,
|
|
2477
|
+
})))
|
|
2478
|
+
.sort((left, right) => left.instance.localeCompare(right.instance) ||
|
|
2479
|
+
left.pattern.localeCompare(right.pattern) ||
|
|
2480
|
+
left.slot.localeCompare(right.slot));
|
|
2352
2481
|
return {
|
|
2353
2482
|
ok: true,
|
|
2354
2483
|
patternMemberships,
|
|
2484
|
+
patternVacancies,
|
|
2355
2485
|
profileContext: {
|
|
2356
2486
|
conceptKindLineages: immutableMap([...conceptKindByIdentity]
|
|
2357
2487
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -2441,6 +2571,7 @@ export function compileWorkspace(sources) {
|
|
|
2441
2571
|
ok: true,
|
|
2442
2572
|
graph: result.graph,
|
|
2443
2573
|
patternMemberships: result.patternMemberships,
|
|
2574
|
+
patternVacancies: result.patternVacancies,
|
|
2444
2575
|
}
|
|
2445
2576
|
: result;
|
|
2446
2577
|
}
|
package/dist/design-command.js
CHANGED
|
@@ -294,7 +294,7 @@ export function runDesignCommand(options, cwd) {
|
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
-
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues, compilation.patternMemberships);
|
|
297
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues, compilation.patternMemberships, compilation.patternVacancies);
|
|
298
298
|
// Keyed by the QUALIFIED id, matching what the report now carries.
|
|
299
299
|
const askPlainById = new Map(composed.composed.catalogue.questions.flatMap((question) => question.askPlain === undefined
|
|
300
300
|
? []
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { constraintExpectsPredicate, reconcileEvidenceReports, type AssertedRela
|
|
|
10
10
|
export { deriveAttestationStaleness } from './attestation-staleness.js';
|
|
11
11
|
export { deriveArtifactCoverage, type ArtifactCoverage, type CoverageScopePattern, } from './artifact-coverage.js';
|
|
12
12
|
export { buildRtm, renderRtmMarkdown, type RequirementsTraceabilityMatrix, type RtmAttestation, type RtmContextEntry, type RtmDescopedEntry, type RtmEvidenceVerdict, type RtmLineageEntry, type RtmRealizer, type RtmRow, type RtmSource, } from './rtm.js';
|
|
13
|
-
export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, PatternMembership, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
13
|
+
export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, PatternMembership, PatternVacancy, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
14
14
|
export { canonicalProjection, evaluateProjection, explainProjection, loadProjection, renderProjectionMarkdown, type ConceptFacet, type ProjectionExclusion, } from './projection.js';
|
|
15
15
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, type AdapterMapping, type AdapterMappingLoadResult, type AdapterMappingValidationResult, type AdapterMappingsValidationResult, type AdapterSubjectMapping, } from './adapter-mapping.js';
|
|
16
16
|
export type { LifecycleStatus, ProjectionDefinition, ProjectionLoadResult, ProjectionResult, } from './projection.js';
|
|
@@ -19,4 +19,4 @@ export { applyOperations, landOperations, posixDirectoryOf, type ApplyInput, typ
|
|
|
19
19
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
20
20
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
21
21
|
export { deletionBlockers, describeDeletion, draftDeletion, type DeletionBlocker, } from './deletion-drafting.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';
|
|
22
|
+
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, conditionInput, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CataloguePatternMembership, type CataloguePatternVacancy, type CatalogueInput, 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
|
@@ -17,4 +17,4 @@ export { applyOperations, landOperations, posixDirectoryOf, } from './apply-comm
|
|
|
17
17
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
18
18
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
19
19
|
export { deletionBlockers, describeDeletion, draftDeletion, } from './deletion-drafting.js';
|
|
20
|
-
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, } from './interrogate-command.js';
|
|
20
|
+
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, conditionInput, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, } from './interrogate-command.js';
|