yarramate 1.16.0 → 1.18.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/ask-command.js +3 -3
- package/dist/compiler.d.ts +28 -0
- package/dist/compiler.js +145 -29
- package/dist/design-command.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/interrogate-command.d.ts +46 -1
- package/dist/interrogate-command.js +24 -5
- package/dist/schema-validators.generated.js +225 -19
- package/dist/visual-app/assets/{index-CV1UFvsk.js → index-CvTDYpur.js} +1 -1
- package/dist/visual-app/index.html +1 -1
- package/dist/visual-app-lib/editor.js +27324 -27031
- 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 +46 -1
- package/dist/visual-app-lib/types/visual-app/local-host.d.ts +34 -1
- package/dist/visual-app-lib/types/visual-app/mount.d.ts +38 -2
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +15 -0
- package/docs/CONSUMING-YARRAMATE.md +27 -0
- package/docs/INTERROGATION.md +19 -0
- package/package.json +1 -1
- 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/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
|
|
@@ -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, compilation.patternMemberships);
|
|
1024
|
+
const report = evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues, compilation.patternMemberships, compilation.patternVacancies);
|
|
1025
1025
|
const openQuestions = [];
|
|
1026
1026
|
for (const wave of report.waves) {
|
|
1027
1027
|
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
|
@@ -846,6 +846,18 @@ function compileWorkspaceResolved(parsed) {
|
|
|
846
846
|
return diagnosticFailure(patternDiagnostics);
|
|
847
847
|
}
|
|
848
848
|
const patternInstances = [];
|
|
849
|
+
/**
|
|
850
|
+
* Concepts whose kind HAS a pattern but which declare no `parts` at all —
|
|
851
|
+
* the greenfield instance ADR 0123 named and left to a later phase, and the
|
|
852
|
+
* one an interview has the most to ask (#447).
|
|
853
|
+
*
|
|
854
|
+
* Deliberately NOT `patternInstances`. That list drives YM416, wiring
|
|
855
|
+
* expansion and membership, and adding these to it would start refusing
|
|
856
|
+
* workspaces that compile today. Instance-hood for the purpose of BEING
|
|
857
|
+
* ASKED is a wider question than instance-hood for the purpose of being
|
|
858
|
+
* expanded, so it gets its own list rather than a widened one.
|
|
859
|
+
*/
|
|
860
|
+
const unbegunInstances = [];
|
|
849
861
|
const documents = documentInputs.map(({ input, entry, fresh }) => {
|
|
850
862
|
// Schema-checked by `parseWorkspaceSource`; the faults it found are the
|
|
851
863
|
// `schemaDiagnostics` returned below, and they gate every later phase.
|
|
@@ -1562,9 +1574,17 @@ function compileWorkspaceResolved(parsed) {
|
|
|
1562
1574
|
// A pattern instance is only COLLECTED here. What its slots bind may be
|
|
1563
1575
|
// declared in any document, so the bindings cannot be checked until
|
|
1564
1576
|
// every document has been read (#268, ADR 0123).
|
|
1577
|
+
const conceptKindIdentity = selectedProfile.conceptKinds.get(concept.kind)?.identity ?? concept.kind;
|
|
1578
|
+
if (concept.parts === undefined) {
|
|
1579
|
+
// Nothing to bind and nothing to expand, but the kind still promises a
|
|
1580
|
+
// shape, so the interview can still ask about it (#447).
|
|
1581
|
+
const pattern = patternsByKind.get(conceptKindIdentity);
|
|
1582
|
+
if (pattern !== undefined) {
|
|
1583
|
+
unbegunInstances.push({ instance: subject, pattern });
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1565
1586
|
if (concept.parts !== undefined) {
|
|
1566
|
-
const kindIdentity =
|
|
1567
|
-
concept.kind;
|
|
1587
|
+
const kindIdentity = conceptKindIdentity;
|
|
1568
1588
|
const pattern = patternsByKind.get(kindIdentity);
|
|
1569
1589
|
if (pattern === undefined) {
|
|
1570
1590
|
const where = location(['concepts', index, 'parts'], `/concepts/${index}/parts`);
|
|
@@ -2067,6 +2087,13 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2067
2087
|
else
|
|
2068
2088
|
group.push(entry);
|
|
2069
2089
|
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Every wire that wants to mint, keyed by the TRIPLE it names (#460).
|
|
2092
|
+
* Collected across all instances before any is minted, because two wires
|
|
2093
|
+
* belonging to different instances can name one triple and only one claim
|
|
2094
|
+
* may survive.
|
|
2095
|
+
*/
|
|
2096
|
+
const wireCandidates = new Map();
|
|
2070
2097
|
for (const { instance, pattern, bindings, sourceOf } of patternInstances) {
|
|
2071
2098
|
const boundTo = new Map();
|
|
2072
2099
|
for (const [slot, target] of bindings) {
|
|
@@ -2173,36 +2200,89 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2173
2200
|
}
|
|
2174
2201
|
if (satisfied)
|
|
2175
2202
|
continue;
|
|
2176
|
-
|
|
2203
|
+
// Collected rather than minted, because whether this wire mints
|
|
2204
|
+
// depends on wires belonging to OTHER instances (#460). Two wires can
|
|
2205
|
+
// name one triple, and before this they both minted: two relationship
|
|
2206
|
+
// claims for one relationship.
|
|
2207
|
+
const key = `${from}\u0000${wire.kindIdentity}\u0000${to}`;
|
|
2208
|
+
const candidate = {
|
|
2177
2209
|
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
|
-
|
|
2210
|
+
pattern,
|
|
2211
|
+
wire,
|
|
2212
|
+
from,
|
|
2213
|
+
to,
|
|
2214
|
+
where,
|
|
2215
|
+
owner: wire.from === 'self',
|
|
2216
|
+
wireId: [
|
|
2217
|
+
instance,
|
|
2218
|
+
...(wire.from === 'self' ? [] : [wire.from]),
|
|
2219
|
+
wire.coreKind,
|
|
2220
|
+
wire.to,
|
|
2221
|
+
].join('-'),
|
|
2222
|
+
};
|
|
2223
|
+
const group = wireCandidates.get(key);
|
|
2224
|
+
if (group === undefined)
|
|
2225
|
+
wireCandidates.set(key, [candidate]);
|
|
2226
|
+
else
|
|
2227
|
+
group.push(candidate);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
// One claim per triple, whichever wires named it (#460, ADR 0141).
|
|
2231
|
+
//
|
|
2232
|
+
// OWNERSHIP decides the id, not walk order. A wire whose `from` is `self`
|
|
2233
|
+
// owns the edge, because the edge leaves that instance; a wire whose
|
|
2234
|
+
// `from` is a slot is a guest naming somebody else's edge, and defers. So
|
|
2235
|
+
// an edge with an owner keeps the ADR 0123 id it has always had, and
|
|
2236
|
+
// nothing moves for a workspace that compiles today.
|
|
2237
|
+
//
|
|
2238
|
+
// Where every wire is a guest the owner's wire is absent — typically its
|
|
2239
|
+
// instance is greenfield, and an unbound slot wires nothing — so there is
|
|
2240
|
+
// no ADR 0123 id to prefer and the id comes from the TRIPLE instead. That
|
|
2241
|
+
// is stable under adding or removing further guests, which sorting a
|
|
2242
|
+
// winner out of the guests would not be.
|
|
2243
|
+
for (const [, group] of [...wireCandidates].sort(([left], [right]) => left.localeCompare(right))) {
|
|
2244
|
+
const owners = group
|
|
2245
|
+
.filter(({ owner }) => owner)
|
|
2246
|
+
// Two owners on one triple cannot happen: `self` differs per instance,
|
|
2247
|
+
// and within one instance YM315 already refuses two slots naming one
|
|
2248
|
+
// subject. Sorted anyway rather than trusting that from a distance.
|
|
2249
|
+
.sort((left, right) => left.wireId.localeCompare(right.wireId));
|
|
2250
|
+
const winner = owners[0] ?? group[0];
|
|
2251
|
+
if (winner === undefined)
|
|
2252
|
+
continue;
|
|
2253
|
+
// A group of ONE is not a collision and keeps its ADR 0123 id, always.
|
|
2254
|
+
// This is the case that matters most, because a wire between two SLOTS
|
|
2255
|
+
// of one pattern (`component --composition--> interface`) has no `self`
|
|
2256
|
+
// endpoint and so no owner, yet nothing is competing with it. Deriving
|
|
2257
|
+
// its id from the triple would rename every such edge in every
|
|
2258
|
+
// workspace, which is exactly the migration ownership exists to avoid;
|
|
2259
|
+
// ADR 0123's own worked example is one of them.
|
|
2260
|
+
const wireId = group.length === 1 || owners.length > 0
|
|
2261
|
+
? winner.wireId
|
|
2262
|
+
: [winner.from, winner.wire.coreKind, winner.to].join('-');
|
|
2263
|
+
if (declaredIds.has(wireId)) {
|
|
2264
|
+
diagnostics.push({
|
|
2265
|
+
severity: 'error',
|
|
2266
|
+
code: 'YM420',
|
|
2267
|
+
message: `The pattern for "${winner.pattern.kindIdentity}" derives the wiring id ` +
|
|
2268
|
+
`"${wireId}" for "${winner.instance}", which is already a declared subject`,
|
|
2269
|
+
path: winner.where.path,
|
|
2270
|
+
pointer: winner.where.pointer,
|
|
2271
|
+
line: winner.where.line,
|
|
2272
|
+
column: winner.where.column,
|
|
2204
2273
|
});
|
|
2274
|
+
continue;
|
|
2205
2275
|
}
|
|
2276
|
+
declaredIds.add(wireId);
|
|
2277
|
+
subjects.push({ id: wireId, type: 'relationship' });
|
|
2278
|
+
claims.push({
|
|
2279
|
+
id: wireId,
|
|
2280
|
+
subject: winner.from,
|
|
2281
|
+
predicate: winner.wire.kindIdentity,
|
|
2282
|
+
object: { ref: winner.to },
|
|
2283
|
+
origin: 'declared',
|
|
2284
|
+
source: winner.where,
|
|
2285
|
+
});
|
|
2206
2286
|
}
|
|
2207
2287
|
}
|
|
2208
2288
|
// ---- macro edges through ports (#268 phase 2, ADR 0124) ------------------
|
|
@@ -2349,9 +2429,44 @@ function compileWorkspaceResolved(parsed) {
|
|
|
2349
2429
|
left.pattern.localeCompare(right.pattern) ||
|
|
2350
2430
|
left.instance.localeCompare(right.instance) ||
|
|
2351
2431
|
left.slot.localeCompare(right.slot));
|
|
2432
|
+
// The mirror (#447): one entry per slot nothing was bound into. Always
|
|
2433
|
+
// emitted, possibly empty — an empty array is a workspace whose instances
|
|
2434
|
+
// are fully bound, while a missing array is a caller that never looked.
|
|
2435
|
+
//
|
|
2436
|
+
// Derived from BOTH lists, and that is the substance of the thing. An
|
|
2437
|
+
// instance that declares some parts is asked about the rest; an instance
|
|
2438
|
+
// that declares none is asked about all of them, because a template nobody
|
|
2439
|
+
// has begun filling in is the one with the most blanks, not the one with
|
|
2440
|
+
// none. Reading only `patternInstances` here would have reported `[]` for
|
|
2441
|
+
// it, and `[]` means "fully bound".
|
|
2442
|
+
const patternVacancies = [
|
|
2443
|
+
...patternInstances.map(({ instance, pattern, bindings }) => ({
|
|
2444
|
+
instance,
|
|
2445
|
+
pattern,
|
|
2446
|
+
bound: bindings,
|
|
2447
|
+
})),
|
|
2448
|
+
...unbegunInstances.map(({ instance, pattern }) => ({
|
|
2449
|
+
instance,
|
|
2450
|
+
pattern,
|
|
2451
|
+
bound: new Map(),
|
|
2452
|
+
})),
|
|
2453
|
+
]
|
|
2454
|
+
.flatMap(({ instance, pattern, bound }) => [...pattern.slots.values()]
|
|
2455
|
+
.filter((slot) => !bound.has(slot.name))
|
|
2456
|
+
.map((slot) => ({
|
|
2457
|
+
instance,
|
|
2458
|
+
pattern: pattern.kindIdentity,
|
|
2459
|
+
slot: slot.name,
|
|
2460
|
+
slotKind: slot.kindIdentity,
|
|
2461
|
+
required: slot.required === true,
|
|
2462
|
+
})))
|
|
2463
|
+
.sort((left, right) => left.instance.localeCompare(right.instance) ||
|
|
2464
|
+
left.pattern.localeCompare(right.pattern) ||
|
|
2465
|
+
left.slot.localeCompare(right.slot));
|
|
2352
2466
|
return {
|
|
2353
2467
|
ok: true,
|
|
2354
2468
|
patternMemberships,
|
|
2469
|
+
patternVacancies,
|
|
2355
2470
|
profileContext: {
|
|
2356
2471
|
conceptKindLineages: immutableMap([...conceptKindByIdentity]
|
|
2357
2472
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -2441,6 +2556,7 @@ export function compileWorkspace(sources) {
|
|
|
2441
2556
|
ok: true,
|
|
2442
2557
|
graph: result.graph,
|
|
2443
2558
|
patternMemberships: result.patternMemberships,
|
|
2559
|
+
patternVacancies: result.patternVacancies,
|
|
2444
2560
|
}
|
|
2445
2561
|
: result;
|
|
2446
2562
|
}
|
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, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CataloguePatternMembership, type CataloguePatternVacancy, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
|
@@ -174,6 +174,25 @@ export type CatalogueCondition = {
|
|
|
174
174
|
readonly condition: 'fills-pattern-slot';
|
|
175
175
|
readonly patternKinds?: readonly string[];
|
|
176
176
|
readonly slots?: readonly string[];
|
|
177
|
+
} | {
|
|
178
|
+
/**
|
|
179
|
+
* The subject is a pattern INSTANCE with a slot nothing is bound into
|
|
180
|
+
* (#447). The mirror of `fills-pattern-slot`, and the mechanism by which
|
|
181
|
+
* a pattern becomes a questionnaire: bare, it means any slot of this
|
|
182
|
+
* instance's pattern is unbound; `patternKinds` narrows by the pattern's
|
|
183
|
+
* kind identity and `slots` by part name, exactly as its mirror does.
|
|
184
|
+
*
|
|
185
|
+
* It fires for a REQUIRED slot too, but only where one can survive a
|
|
186
|
+
* compile: an instance that declares `parts` and omits a required one is
|
|
187
|
+
* YM416 and there is no result to read, while an instance that declares
|
|
188
|
+
* no `parts` at all never reaches YM416 and is exactly the greenfield
|
|
189
|
+
* case ADR 0123 left open. The vacancy row's `required` tells the two
|
|
190
|
+
* apart for the host; the condition itself does not read it, because a
|
|
191
|
+
* catalogue that wants only one of them says so with `slots`.
|
|
192
|
+
*/
|
|
193
|
+
readonly condition: 'missing-part';
|
|
194
|
+
readonly patternKinds?: readonly string[];
|
|
195
|
+
readonly slots?: readonly string[];
|
|
177
196
|
};
|
|
178
197
|
/**
|
|
179
198
|
* One observation from the workspace's evidence overlay, reduced to what
|
|
@@ -207,6 +226,26 @@ export interface CataloguePatternMembership {
|
|
|
207
226
|
readonly instance: string;
|
|
208
227
|
readonly pattern: string;
|
|
209
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* One vacant optional slot of one pattern instance, as interrogation reads
|
|
231
|
+
* it. Structural for the same reason {@link CataloguePatternMembership} is:
|
|
232
|
+
* a host passes `compilation.patternVacancies` and the compiler's types are
|
|
233
|
+
* never dragged into the pure engine entry. Only `missing-part` reads it.
|
|
234
|
+
*/
|
|
235
|
+
export interface CataloguePatternVacancy {
|
|
236
|
+
readonly instance: string;
|
|
237
|
+
readonly pattern: string;
|
|
238
|
+
readonly slot: string;
|
|
239
|
+
readonly slotKind: string;
|
|
240
|
+
/**
|
|
241
|
+
* The pattern declares this part required. The condition does NOT read it —
|
|
242
|
+
* a vacancy is a vacancy — but it travels because the host derives its
|
|
243
|
+
* answer shape from this row, and "you have not decided this yet" and "this
|
|
244
|
+
* model does not stand up without it" are different questions to put to a
|
|
245
|
+
* person (#447).
|
|
246
|
+
*/
|
|
247
|
+
readonly required: boolean;
|
|
248
|
+
}
|
|
210
249
|
export interface CatalogueQuestion {
|
|
211
250
|
readonly id: string;
|
|
212
251
|
readonly wave: string;
|
|
@@ -329,7 +368,13 @@ catalogues?: readonly string[],
|
|
|
329
368
|
* `catalogues` is a fifth: this signature is published and a consumer
|
|
330
369
|
* already calls it.
|
|
331
370
|
*/
|
|
332
|
-
patternMemberships?: readonly CataloguePatternMembership[]
|
|
371
|
+
patternMemberships?: readonly CataloguePatternMembership[],
|
|
372
|
+
/**
|
|
373
|
+
* Pattern vacancies from the compilation (#447) - pass
|
|
374
|
+
* `compilation.patternVacancies`, or `missing-part` conditions never fire.
|
|
375
|
+
* A seventh optional parameter for the same reason the sixth is one.
|
|
376
|
+
*/
|
|
377
|
+
patternVacancies?: readonly CataloguePatternVacancy[]): Omit<InterrogationReport, 'workspace'>;
|
|
333
378
|
export type CatalogueLoadResult = {
|
|
334
379
|
readonly ok: true;
|
|
335
380
|
readonly catalogue: QuestionCatalogue;
|
|
@@ -274,10 +274,23 @@ const CONDITION_SCOPE = {
|
|
|
274
274
|
'unscoped-succession': 'subject',
|
|
275
275
|
'unchallenged-evidence': 'workspace',
|
|
276
276
|
'fills-pattern-slot': 'subject',
|
|
277
|
+
'missing-part': 'subject',
|
|
277
278
|
};
|
|
278
279
|
export const conditionScope = (condition) => CONDITION_SCOPE[condition.condition];
|
|
279
|
-
const conditionHolds = (index, condition, subjectId, profileContext, evidence, memberships) => {
|
|
280
|
+
const conditionHolds = (index, condition, subjectId, profileContext, evidence, memberships, vacancies) => {
|
|
280
281
|
switch (condition.condition) {
|
|
282
|
+
case 'missing-part':
|
|
283
|
+
// Absent vacancies stay quiet, exactly as absent memberships do below:
|
|
284
|
+
// the caller did not derive them, so what is unbound is unknown rather
|
|
285
|
+
// than nothing. An EMPTY array is the opposite and says so - every
|
|
286
|
+
// instance is fully bound (#447, CONTRIBUTING's second rule).
|
|
287
|
+
return (vacancies !== undefined &&
|
|
288
|
+
subjectId !== undefined &&
|
|
289
|
+
vacancies.some((vacancy) => vacancy.instance === subjectId &&
|
|
290
|
+
(condition.patternKinds === undefined ||
|
|
291
|
+
condition.patternKinds.includes(vacancy.pattern)) &&
|
|
292
|
+
(condition.slots === undefined ||
|
|
293
|
+
condition.slots.includes(vacancy.slot))));
|
|
281
294
|
case 'fills-pattern-slot':
|
|
282
295
|
// Absent memberships stay quiet: the caller did not derive them, so
|
|
283
296
|
// participation is unknown, not absent — the same rule
|
|
@@ -553,13 +566,19 @@ catalogues,
|
|
|
553
566
|
* `catalogues` is a fifth: this signature is published and a consumer
|
|
554
567
|
* already calls it.
|
|
555
568
|
*/
|
|
556
|
-
patternMemberships
|
|
569
|
+
patternMemberships,
|
|
570
|
+
/**
|
|
571
|
+
* Pattern vacancies from the compilation (#447) - pass
|
|
572
|
+
* `compilation.patternVacancies`, or `missing-part` conditions never fire.
|
|
573
|
+
* A seventh optional parameter for the same reason the sixth is one.
|
|
574
|
+
*/
|
|
575
|
+
patternVacancies) {
|
|
557
576
|
const index = indexGraph(graph);
|
|
558
577
|
let open = 0;
|
|
559
578
|
let openQuestions = 0;
|
|
560
579
|
const applicableQuestions = catalogue.questions.filter((question) => questionIsApplicable(question, graph.profiles));
|
|
561
580
|
const waveOpens = (wave) => wave.opensWhen === undefined ||
|
|
562
|
-
wave.opensWhen.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence, patternMemberships));
|
|
581
|
+
wave.opensWhen.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence, patternMemberships, patternVacancies));
|
|
563
582
|
const waves = catalogue.waves.map((wave) => ({
|
|
564
583
|
id: wave.id,
|
|
565
584
|
name: wave.name,
|
|
@@ -584,7 +603,7 @@ patternMemberships) {
|
|
|
584
603
|
...(question.since === undefined ? {} : { since: question.since }),
|
|
585
604
|
};
|
|
586
605
|
if (question.scope === 'workspace') {
|
|
587
|
-
const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence, patternMemberships));
|
|
606
|
+
const isOpen = question.trigger.every((condition) => conditionHolds(index, condition, undefined, profileContext, evidence, patternMemberships, patternVacancies));
|
|
588
607
|
if (isOpen) {
|
|
589
608
|
open += 1;
|
|
590
609
|
openQuestions += 1;
|
|
@@ -600,7 +619,7 @@ patternMemberships) {
|
|
|
600
619
|
if (selected.length === 0) {
|
|
601
620
|
return { ...base, open: false, asked: false };
|
|
602
621
|
}
|
|
603
|
-
const matches = selected.filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence, patternMemberships)));
|
|
622
|
+
const matches = selected.filter((id) => question.trigger.every((condition) => conditionHolds(index, condition, id, profileContext, evidence, patternMemberships, patternVacancies)));
|
|
604
623
|
if (matches.length === 0) {
|
|
605
624
|
return { ...base, open: false };
|
|
606
625
|
}
|