yarramate 1.11.0 → 1.12.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/interrogate-command.d.ts +17 -0
- package/dist/interrogate-command.js +93 -0
- package/dist/visual-app/assets/index-BD9s1WI8.js +394 -0
- package/dist/visual-app/index.html +1 -1
- package/dist/visual-app-lib/editor.js +26945 -26913
- package/dist/visual-app-lib/types/interrogate-command.d.ts +17 -0
- package/docs/MODEL-FLOOR.md +229 -0
- package/package.json +3 -2
- package/schema/yarramate-design-step.schema.json +28 -0
- package/schema/yarramate-interrogation-report.schema.json +28 -0
- package/schema/yarramate-question-catalogue.schema.json +28 -0
- package/dist/visual-app/assets/index-Cuj0wIhg.js +0 -394
|
@@ -46,6 +46,22 @@ export type CatalogueCondition = {
|
|
|
46
46
|
readonly condition: 'no-subject-of-kind';
|
|
47
47
|
readonly kinds: readonly string[];
|
|
48
48
|
readonly kindMatching?: 'exact' | 'descendants';
|
|
49
|
+
} | {
|
|
50
|
+
/**
|
|
51
|
+
* The positive twin of `no-subject-of-kind`, and workspace-scope like it
|
|
52
|
+
* (#398). A gate wants the opposite polarity from a question: a question
|
|
53
|
+
* exists to be closed by an absence ending, while a gate opens once the
|
|
54
|
+
* thing is there. `opensWhen` requires every condition to hold and has no
|
|
55
|
+
* `not`, so inverting was not available and the phase-ordered interview
|
|
56
|
+
* an adopter was authoring could say "the model still lacks X" but never
|
|
57
|
+
* "the model now has X".
|
|
58
|
+
*
|
|
59
|
+
* ADR 0125 anticipated arrivals in this position: a further condition
|
|
60
|
+
* "can join later without changing the mechanism".
|
|
61
|
+
*/
|
|
62
|
+
readonly condition: 'has-subject-of-kind';
|
|
63
|
+
readonly kinds: readonly string[];
|
|
64
|
+
readonly kindMatching?: 'exact' | 'descendants';
|
|
49
65
|
} | {
|
|
50
66
|
readonly condition: 'no-state-defined';
|
|
51
67
|
} | {
|
|
@@ -238,6 +254,7 @@ export interface InterrogationReport {
|
|
|
238
254
|
readonly summary: InterrogationSummary;
|
|
239
255
|
readonly waves: readonly ReportWave[];
|
|
240
256
|
}
|
|
257
|
+
export declare const conditionScope: (condition: CatalogueCondition) => 'workspace' | 'subject';
|
|
241
258
|
export declare const renderQuestion: (template: string, subjectId: string, subjectName: string | undefined, counterparts?: readonly string[]) => string;
|
|
242
259
|
export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext, evidence?: readonly CatalogueEvidenceObservation[],
|
|
243
260
|
/**
|
|
@@ -199,6 +199,7 @@ const namedKinds = (question) => {
|
|
|
199
199
|
switch (condition.condition) {
|
|
200
200
|
case 'missing-relationship':
|
|
201
201
|
case 'no-subject-of-kind':
|
|
202
|
+
case 'has-subject-of-kind':
|
|
202
203
|
case 'missing-constraint':
|
|
203
204
|
kinds.push(...condition.kinds);
|
|
204
205
|
break;
|
|
@@ -237,6 +238,49 @@ const linkageHits = (index, condition, subjectId, profileContext) => {
|
|
|
237
238
|
return counterparts.some((counterpart) => kindMatches(index.kindOf.get(counterpart), condition.counterpartKinds, matching, profileContext));
|
|
238
239
|
});
|
|
239
240
|
};
|
|
241
|
+
/**
|
|
242
|
+
* What a condition needs in order to mean anything: a subject, or only the
|
|
243
|
+
* workspace (#400).
|
|
244
|
+
*
|
|
245
|
+
* A wave gate evaluates with NO subject, and the catalogue schema offered the
|
|
246
|
+
* whole vocabulary in that position, so a subject-scope condition in
|
|
247
|
+
* `opensWhen` produced a wave that silently never opened (`has-linkage`,
|
|
248
|
+
* `near-duplicate`, `fills-pattern-slot`) or a gate that was silently inert
|
|
249
|
+
* (`missing-linkage`, `isolated`, `missing-claim`, `missing-constraint`) —
|
|
250
|
+
* measured, both halves. Neither was refused. That is the same failure
|
|
251
|
+
* `YM914` already refuses from a different cause: a gate nothing can satisfy
|
|
252
|
+
* is indistinguishable from a gate that is merely unmet.
|
|
253
|
+
*
|
|
254
|
+
* This is a `Record` over the union's discriminant rather than a list of the
|
|
255
|
+
* workspace-scope names, and that is the point. An allowlist cannot fail for
|
|
256
|
+
* the author who wrote it (CONTRIBUTING.md's ninth rule), so a new condition
|
|
257
|
+
* must not be able to arrive and be quietly absent from a gate check. Here it
|
|
258
|
+
* cannot: adding a member to `CatalogueCondition` is a TYPECHECK ERROR until
|
|
259
|
+
* its scope is declared, so the compiler asks the question rather than this
|
|
260
|
+
* table remembering the answer.
|
|
261
|
+
*/
|
|
262
|
+
const CONDITION_SCOPE = {
|
|
263
|
+
'has-any-subject': 'workspace',
|
|
264
|
+
'no-subject-of-kind': 'workspace',
|
|
265
|
+
'has-subject-of-kind': 'workspace',
|
|
266
|
+
'no-state-defined': 'workspace',
|
|
267
|
+
'exists-linkage': 'workspace',
|
|
268
|
+
'missing-claim': 'subject',
|
|
269
|
+
'missing-relationship': 'subject',
|
|
270
|
+
isolated: 'subject',
|
|
271
|
+
'missing-linkage': 'subject',
|
|
272
|
+
'has-linkage': 'subject',
|
|
273
|
+
'missing-constraint': 'subject',
|
|
274
|
+
'missing-flow-content': 'subject',
|
|
275
|
+
'missing-reference': 'subject',
|
|
276
|
+
'missing-attestation': 'subject',
|
|
277
|
+
'near-duplicate': 'subject',
|
|
278
|
+
'unconstrained-kind': 'subject',
|
|
279
|
+
'unscoped-succession': 'subject',
|
|
280
|
+
'unchallenged-evidence': 'workspace',
|
|
281
|
+
'fills-pattern-slot': 'subject',
|
|
282
|
+
};
|
|
283
|
+
export const conditionScope = (condition) => CONDITION_SCOPE[condition.condition];
|
|
240
284
|
const conditionHolds = (index, condition, subjectId, profileContext, evidence, memberships) => {
|
|
241
285
|
switch (condition.condition) {
|
|
242
286
|
case 'fills-pattern-slot':
|
|
@@ -325,6 +369,14 @@ const conditionHolds = (index, condition, subjectId, profileContext, evidence, m
|
|
|
325
369
|
const matching = condition.kindMatching ?? 'descendants';
|
|
326
370
|
return ![...index.concepts].some((id) => kindMatches(index.kindOf.get(id), condition.kinds, matching, profileContext));
|
|
327
371
|
}
|
|
372
|
+
case 'has-subject-of-kind': {
|
|
373
|
+
// Deliberately not `!no-subject-of-kind`: written as its own existence
|
|
374
|
+
// check so the empty workspace falls out right rather than by double
|
|
375
|
+
// negative. No subjects of any kind means every gate using it stays
|
|
376
|
+
// shut, which is the #334 posture.
|
|
377
|
+
const matching = condition.kindMatching ?? 'descendants';
|
|
378
|
+
return [...index.concepts].some((id) => kindMatches(index.kindOf.get(id), condition.kinds, matching, profileContext));
|
|
379
|
+
}
|
|
328
380
|
case 'no-state-defined':
|
|
329
381
|
return !index.hasStates;
|
|
330
382
|
case 'missing-linkage':
|
|
@@ -813,6 +865,39 @@ const undeclaredWaveDiagnostics = ({ catalogue, locate }, declaredWaves) => cata
|
|
|
813
865
|
...locate(['questions', questionIndex, 'wave']),
|
|
814
866
|
},
|
|
815
867
|
]);
|
|
868
|
+
/**
|
|
869
|
+
* `YM917`: a wave gate may only ask about the workspace (#400).
|
|
870
|
+
*
|
|
871
|
+
* `opensWhen` is evaluated with no subject, so a subject-scope condition here
|
|
872
|
+
* cannot mean what it reads as. Half of them then leave the wave permanently
|
|
873
|
+
* shut and half leave the gate inert, and an author reviewing the YAML sees a
|
|
874
|
+
* gate either way — the same invisible failure `YM914` refuses when a gate
|
|
875
|
+
* names a kind that resolves nowhere.
|
|
876
|
+
*
|
|
877
|
+
* Refused at load rather than narrowed in the schema on purpose. The schema
|
|
878
|
+
* could express it as a second `oneOf`, but a `oneOf` miss reports "must match
|
|
879
|
+
* exactly one schema", which names neither the offending condition nor the
|
|
880
|
+
* ones that would work — and this diagnostic exists precisely because the
|
|
881
|
+
* author cannot see the problem.
|
|
882
|
+
*/
|
|
883
|
+
const gateScopeDiagnostics = ({ catalogue, locate, }) => catalogue.waves.flatMap((wave, waveIndex) => (wave.opensWhen ?? []).flatMap((condition, conditionIndex) => conditionScope(condition) === 'subject'
|
|
884
|
+
? [
|
|
885
|
+
{
|
|
886
|
+
severity: 'error',
|
|
887
|
+
code: 'YM917',
|
|
888
|
+
message: `Condition "${condition.condition}" gates wave "${wave.id}" but asks about a subject, ` +
|
|
889
|
+
'and a gate is evaluated with none, so the wave would never open or the gate would do ' +
|
|
890
|
+
`nothing. Gate on the workspace instead: ${workspaceScopeConditions().join(', ')}.`,
|
|
891
|
+
...locate(['waves', waveIndex, 'opensWhen', conditionIndex]),
|
|
892
|
+
},
|
|
893
|
+
]
|
|
894
|
+
: []));
|
|
895
|
+
// Read off the scope table rather than restated, so the remedy a diagnostic
|
|
896
|
+
// offers cannot drift from the set the engine actually accepts.
|
|
897
|
+
const workspaceScopeConditions = () => Object.entries(CONDITION_SCOPE)
|
|
898
|
+
.filter(([, scope]) => scope === 'workspace')
|
|
899
|
+
.map(([condition]) => condition)
|
|
900
|
+
.sort();
|
|
816
901
|
const unresolvableKindDiagnostics = ({ catalogue, locate }, profileContext) =>
|
|
817
902
|
// Only when a caller has a compiled workspace to check against. Without one
|
|
818
903
|
// there is no way to tell a typo from a kind whose profile simply is not
|
|
@@ -951,6 +1036,7 @@ export function composeCatalogues(sources, profileContext) {
|
|
|
951
1036
|
const declaredWaves = new Set(declaredBy.keys());
|
|
952
1037
|
const crossDiagnostics = documents.flatMap((document) => [
|
|
953
1038
|
...undeclaredWaveDiagnostics(document, declaredWaves),
|
|
1039
|
+
...gateScopeDiagnostics(document),
|
|
954
1040
|
...unresolvableKindDiagnostics(document, profileContext),
|
|
955
1041
|
...unauthorableOfferDiagnostics(document, profileContext),
|
|
956
1042
|
]);
|
|
@@ -985,6 +1071,13 @@ export function loadQuestionCatalogue(catalogueSource, profileContext) {
|
|
|
985
1071
|
if (waveDiagnostics.length > 0) {
|
|
986
1072
|
return { ok: false, diagnostics: waveDiagnostics };
|
|
987
1073
|
}
|
|
1074
|
+
// Before the kind checks, and unlike them it needs no profile context: a
|
|
1075
|
+
// gate that asks about a subject is wrong on its own terms, whether or not
|
|
1076
|
+
// a caller brought a compiled workspace to resolve kinds against.
|
|
1077
|
+
const scopeDiagnostics = gateScopeDiagnostics(loaded.document);
|
|
1078
|
+
if (scopeDiagnostics.length > 0) {
|
|
1079
|
+
return { ok: false, diagnostics: scopeDiagnostics };
|
|
1080
|
+
}
|
|
988
1081
|
const kindDiagnostics = unresolvableKindDiagnostics(loaded.document, profileContext);
|
|
989
1082
|
if (kindDiagnostics.length > 0) {
|
|
990
1083
|
return { ok: false, diagnostics: kindDiagnostics };
|