yarramate 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/catalogues/core-enrichment.yaml +40 -6
- package/dist/interrogate-command.d.ts +28 -0
- package/dist/interrogate-command.js +83 -0
- package/dist/visual-app-lib/editor.js +25931 -25891
- package/dist/visual-app-lib/types/interrogate-command.d.ts +28 -0
- package/docs/MODEL-FLOOR.md +36 -0
- package/package.json +1 -1
- package/schema/yarramate-design-step.schema.json +34 -0
- package/schema/yarramate-interrogation-report.schema.json +34 -0
- package/schema/yarramate-question-catalogue.schema.json +34 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
format: yarramate/question-catalogue/v1
|
|
2
2
|
id: core-enrichment
|
|
3
|
-
version: "
|
|
3
|
+
version: "2.0"
|
|
4
4
|
profile: yarramate/core@0.1
|
|
5
5
|
presentation:
|
|
6
6
|
title: Core enrichment interview
|
|
@@ -36,21 +36,35 @@ waves:
|
|
|
36
36
|
name: Application
|
|
37
37
|
description: >-
|
|
38
38
|
How declared services are realized, performed, and fed with
|
|
39
|
-
information.
|
|
39
|
+
information. Waits for a declared service, because that is what this
|
|
40
|
+
wave asks about the realization of. The kinds here are exactly the ones
|
|
41
|
+
`no-service-declared` asks for, so this wave opens precisely when that
|
|
42
|
+
question closes.
|
|
40
43
|
opensWhen:
|
|
41
|
-
- condition: has-
|
|
44
|
+
- condition: has-subject-of-kind
|
|
45
|
+
kinds:
|
|
46
|
+
- yarramate/core@0.1#businessService
|
|
47
|
+
- yarramate/core@0.1#applicationService
|
|
48
|
+
- yarramate/core@0.1#technologyService
|
|
42
49
|
- id: technology
|
|
43
50
|
name: Technology
|
|
44
51
|
description: >-
|
|
45
52
|
Where the declared applications actually run and what materializes
|
|
46
|
-
them.
|
|
53
|
+
them. Waits for a declared application component, because asking
|
|
54
|
+
where something runs before anything is declared to run is a question
|
|
55
|
+
with no subject.
|
|
47
56
|
opensWhen:
|
|
48
|
-
- condition: has-
|
|
57
|
+
- condition: has-subject-of-kind
|
|
58
|
+
kinds:
|
|
59
|
+
- yarramate/core@0.1#applicationComponent
|
|
49
60
|
- id: implementation
|
|
50
61
|
name: Implementation
|
|
51
62
|
description: >-
|
|
52
63
|
How the planned architecture becomes real: work, deliverables, and
|
|
53
|
-
the plateaus between here and there.
|
|
64
|
+
the plateaus between here and there. NOT phase-gated, deliberately:
|
|
65
|
+
every candidate gate names something this wave exists to elicit, and
|
|
66
|
+
a declared state compiles to a plateau, which closes this wave's own
|
|
67
|
+
lead question. See ADR 0136.
|
|
54
68
|
opensWhen:
|
|
55
69
|
- condition: has-any-subject
|
|
56
70
|
- id: hygiene
|
|
@@ -918,6 +932,26 @@ questions:
|
|
|
918
932
|
seven usually carries the whole conversation — and relate each to
|
|
919
933
|
the services or components that realize it with realization.
|
|
920
934
|
|
|
935
|
+
- id: no-component-declared
|
|
936
|
+
wave: business
|
|
937
|
+
since: "2.0"
|
|
938
|
+
scope: workspace
|
|
939
|
+
trigger:
|
|
940
|
+
- condition: no-subject-of-kind
|
|
941
|
+
kinds:
|
|
942
|
+
- yarramate/core@0.1#applicationComponent
|
|
943
|
+
question: >-
|
|
944
|
+
What software actually implements these services?
|
|
945
|
+
askPlain: >-
|
|
946
|
+
In plain terms: which applications or systems does this run on?
|
|
947
|
+
materiality: >-
|
|
948
|
+
A model with services and nothing implementing them cannot say where
|
|
949
|
+
anything runs, so the whole technology conversation has no subject.
|
|
950
|
+
This question exists to keep that conversation reachable: the
|
|
951
|
+
technology wave opens exactly when this question closes (ADR 0136).
|
|
952
|
+
authority: human
|
|
953
|
+
resolution: >-
|
|
954
|
+
Add the application components that implement the declared services.
|
|
921
955
|
- id: no-contract-declared
|
|
922
956
|
wave: business
|
|
923
957
|
since: "1.2"
|
|
@@ -62,6 +62,34 @@ export type CatalogueCondition = {
|
|
|
62
62
|
readonly condition: 'has-subject-of-kind';
|
|
63
63
|
readonly kinds: readonly string[];
|
|
64
64
|
readonly kindMatching?: 'exact' | 'descendants';
|
|
65
|
+
} | {
|
|
66
|
+
/**
|
|
67
|
+
* The cardinality member of the same family, workspace-scope like the
|
|
68
|
+
* other two (#411). Fires while FEWER than `atLeast` subjects of the
|
|
69
|
+
* named kinds exist, so `no-subject-of-kind` is its `atLeast: 1` case.
|
|
70
|
+
*
|
|
71
|
+
* It exists because a **vocabulary question** — "which data sensitivity
|
|
72
|
+
* classes does this platform recognise?" — had no way to say it wanted
|
|
73
|
+
* more than one term. `no-subject-of-kind` closes on the first
|
|
74
|
+
* instance, so a one-term vocabulary was indistinguishable from a
|
|
75
|
+
* complete one, and an adopter measured two live cases where a class
|
|
76
|
+
* authored incidentally to answer a different question closed the
|
|
77
|
+
* vocabulary question before it was ever asked.
|
|
78
|
+
*
|
|
79
|
+
* `atLeast` is required and must be at least 2 (`YM918`): 1 is
|
|
80
|
+
* `no-subject-of-kind` spelled a second way, and 0 is a condition that
|
|
81
|
+
* can never fire, which is what `YM914` exists to refuse.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately NOT a general numeric comparison. A kind's population
|
|
84
|
+
* against a floor is the whole need, and the narrow condition is the
|
|
85
|
+
* same trade that kept `has-subject-of-kind` a twin rather than a
|
|
86
|
+
* predicate: a vocabulary of parameterised comparisons is a query
|
|
87
|
+
* language, which this design declines.
|
|
88
|
+
*/
|
|
89
|
+
readonly condition: 'below-subject-count';
|
|
90
|
+
readonly kinds: readonly string[];
|
|
91
|
+
readonly atLeast: number;
|
|
92
|
+
readonly kindMatching?: 'exact' | 'descendants';
|
|
65
93
|
} | {
|
|
66
94
|
readonly condition: 'no-state-defined';
|
|
67
95
|
} | {
|
|
@@ -200,6 +200,7 @@ const namedKinds = (question) => {
|
|
|
200
200
|
case 'missing-relationship':
|
|
201
201
|
case 'no-subject-of-kind':
|
|
202
202
|
case 'has-subject-of-kind':
|
|
203
|
+
case 'below-subject-count':
|
|
203
204
|
case 'missing-constraint':
|
|
204
205
|
kinds.push(...condition.kinds);
|
|
205
206
|
break;
|
|
@@ -263,6 +264,7 @@ const CONDITION_SCOPE = {
|
|
|
263
264
|
'has-any-subject': 'workspace',
|
|
264
265
|
'no-subject-of-kind': 'workspace',
|
|
265
266
|
'has-subject-of-kind': 'workspace',
|
|
267
|
+
'below-subject-count': 'workspace',
|
|
266
268
|
'no-state-defined': 'workspace',
|
|
267
269
|
'exists-linkage': 'workspace',
|
|
268
270
|
'missing-claim': 'subject',
|
|
@@ -377,6 +379,27 @@ const conditionHolds = (index, condition, subjectId, profileContext, evidence, m
|
|
|
377
379
|
const matching = condition.kindMatching ?? 'descendants';
|
|
378
380
|
return [...index.concepts].some((id) => kindMatches(index.kindOf.get(id), condition.kinds, matching, profileContext));
|
|
379
381
|
}
|
|
382
|
+
case 'below-subject-count': {
|
|
383
|
+
// Counts, then compares. Written as its own count rather than as
|
|
384
|
+
// `!has-subject-of-kind` plus arithmetic so the degenerate reading
|
|
385
|
+
// stays visible: at `atLeast: 1` this IS `no-subject-of-kind`, which
|
|
386
|
+
// is why the loader refuses that spelling rather than quietly
|
|
387
|
+
// accepting two names for one condition.
|
|
388
|
+
//
|
|
389
|
+
// Stops at the threshold instead of counting the whole workspace: the
|
|
390
|
+
// answer is a comparison, not a tally, and a model with ten thousand
|
|
391
|
+
// subjects of a kind should cost the same as one with two.
|
|
392
|
+
const matching = condition.kindMatching ?? 'descendants';
|
|
393
|
+
let seen = 0;
|
|
394
|
+
for (const id of index.concepts) {
|
|
395
|
+
if (kindMatches(index.kindOf.get(id), condition.kinds, matching, profileContext)) {
|
|
396
|
+
seen += 1;
|
|
397
|
+
if (seen >= condition.atLeast)
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
380
403
|
case 'no-state-defined':
|
|
381
404
|
return !index.hasStates;
|
|
382
405
|
case 'missing-linkage':
|
|
@@ -633,6 +656,59 @@ patternMemberships) {
|
|
|
633
656
|
* wave never opens and carries no questions at all - one typo silently
|
|
634
657
|
* retiring a wave (#351).
|
|
635
658
|
*/
|
|
659
|
+
/**
|
|
660
|
+
* Every condition the catalogue holds, gate and trigger alike, with the path
|
|
661
|
+
* that locates it. `kindReferencesOf` walks the same two places for kinds;
|
|
662
|
+
* this walks them for the conditions themselves, so a check about a
|
|
663
|
+
* condition's own shape does not have to re-derive where conditions live.
|
|
664
|
+
*/
|
|
665
|
+
const conditionsOf = (catalogue) => {
|
|
666
|
+
const found = [];
|
|
667
|
+
catalogue.waves.forEach((wave, waveIndex) => {
|
|
668
|
+
;
|
|
669
|
+
(wave.opensWhen ?? []).forEach((condition, conditionIndex) => {
|
|
670
|
+
found.push({
|
|
671
|
+
condition,
|
|
672
|
+
path: ['waves', waveIndex, 'opensWhen', conditionIndex],
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
catalogue.questions.forEach((question, questionIndex) => {
|
|
677
|
+
question.trigger.forEach((condition, conditionIndex) => {
|
|
678
|
+
found.push({
|
|
679
|
+
condition,
|
|
680
|
+
path: ['questions', questionIndex, 'trigger', conditionIndex],
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
return found;
|
|
685
|
+
};
|
|
686
|
+
/**
|
|
687
|
+
* `YM918`: `below-subject-count` must ask for at least two (#411).
|
|
688
|
+
*
|
|
689
|
+
* `atLeast: 1` is `no-subject-of-kind` under a second name, and this design
|
|
690
|
+
* refuses second spellings of one meaning wherever it finds them. `atLeast: 0`
|
|
691
|
+
* is worse: the count can never be below zero, so the question can never fire,
|
|
692
|
+
* which is what `YM914` refuses from a different cause.
|
|
693
|
+
*
|
|
694
|
+
* The message names `no-subject-of-kind` rather than stating a bound, because
|
|
695
|
+
* an author who wrote `atLeast: 1` did not make an arithmetic mistake. They
|
|
696
|
+
* wanted the condition that already exists.
|
|
697
|
+
*/
|
|
698
|
+
const subjectCountFloorDiagnostics = ({ catalogue, locate, }) => conditionsOf(catalogue).flatMap(({ condition, path }) => condition.condition === 'below-subject-count' && condition.atLeast < 2
|
|
699
|
+
? [
|
|
700
|
+
{
|
|
701
|
+
severity: 'error',
|
|
702
|
+
code: 'YM918',
|
|
703
|
+
message: `Condition "below-subject-count" asks for atLeast ${condition.atLeast}, ` +
|
|
704
|
+
(condition.atLeast === 1
|
|
705
|
+
? 'which is what "no-subject-of-kind" already asks. Use that condition instead.'
|
|
706
|
+
: 'so a count could never fall below it and the question could never fire. ' +
|
|
707
|
+
'Ask for at least 2, or use "no-subject-of-kind" for the presence case.'),
|
|
708
|
+
...locate([...path, 'atLeast']),
|
|
709
|
+
},
|
|
710
|
+
]
|
|
711
|
+
: []);
|
|
636
712
|
const kindReferencesOf = (catalogue) => {
|
|
637
713
|
const found = [];
|
|
638
714
|
const fromCondition = (condition, path) => {
|
|
@@ -1037,6 +1113,7 @@ export function composeCatalogues(sources, profileContext) {
|
|
|
1037
1113
|
const crossDiagnostics = documents.flatMap((document) => [
|
|
1038
1114
|
...undeclaredWaveDiagnostics(document, declaredWaves),
|
|
1039
1115
|
...gateScopeDiagnostics(document),
|
|
1116
|
+
...subjectCountFloorDiagnostics(document),
|
|
1040
1117
|
...unresolvableKindDiagnostics(document, profileContext),
|
|
1041
1118
|
...unauthorableOfferDiagnostics(document, profileContext),
|
|
1042
1119
|
]);
|
|
@@ -1078,6 +1155,12 @@ export function loadQuestionCatalogue(catalogueSource, profileContext) {
|
|
|
1078
1155
|
if (scopeDiagnostics.length > 0) {
|
|
1079
1156
|
return { ok: false, diagnostics: scopeDiagnostics };
|
|
1080
1157
|
}
|
|
1158
|
+
// Beside the scope check and for the same reason: a threshold below 2 is
|
|
1159
|
+
// wrong on the condition's own terms, with no profile context needed.
|
|
1160
|
+
const floorDiagnostics = subjectCountFloorDiagnostics(loaded.document);
|
|
1161
|
+
if (floorDiagnostics.length > 0) {
|
|
1162
|
+
return { ok: false, diagnostics: floorDiagnostics };
|
|
1163
|
+
}
|
|
1081
1164
|
const kindDiagnostics = unresolvableKindDiagnostics(loaded.document, profileContext);
|
|
1082
1165
|
if (kindDiagnostics.length > 0) {
|
|
1083
1166
|
return { ok: false, diagnostics: kindDiagnostics };
|