yarramate 1.2.0 → 1.3.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 +225 -2
- package/dist/adapters/visual/session-server.js +16 -1
- package/dist/adapters/visual/view-identity.d.ts +1 -1
- package/dist/adapters/visual/view-identity.js +61 -3
- package/dist/adapters/visual/wire.d.ts +30 -0
- package/dist/adapters/visual/workspace-model.d.ts +23 -3
- package/dist/adapters/visual/workspace-model.js +52 -2
- package/dist/ask-command.js +29 -13
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +26 -5
- package/dist/concept-drafting.d.ts +13 -2
- package/dist/concept-drafting.js +15 -3
- package/dist/design-command.js +16 -1
- package/dist/evidence.js +25 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/interrogate-command.d.ts +20 -1
- package/dist/interrogate-command.js +23 -4
- package/dist/interrogation-entry.d.ts +1 -1
- package/dist/layout-direction.d.ts +14 -0
- package/dist/layout-direction.js +5 -0
- package/dist/projection.d.ts +22 -6
- package/dist/projection.js +23 -2
- package/dist/relationship-drafting.d.ts +21 -2
- package/dist/relationship-drafting.js +33 -3
- package/dist/subject-references.js +5 -0
- package/dist/visual-app/assets/index-C3i9SxTe.js +394 -0
- package/dist/visual-app/assets/index-Rkq6smL2.css +1 -0
- package/dist/visual-app/index.html +2 -2
- package/dist/visual-app-lib/editor.js +28464 -26953
- package/dist/visual-app-lib/styles.css +1 -1
- package/dist/visual-app-lib/types/adapters/visual/view-identity.d.ts +1 -1
- package/dist/visual-app-lib/types/adapters/visual/wire.d.ts +30 -0
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +23 -3
- package/dist/visual-app-lib/types/concept-drafting.d.ts +13 -2
- package/dist/visual-app-lib/types/interrogate-command.d.ts +190 -0
- package/dist/visual-app-lib/types/layout-direction.d.ts +14 -0
- package/dist/visual-app-lib/types/projection.d.ts +22 -6
- package/dist/visual-app-lib/types/relationship-drafting.d.ts +21 -2
- package/dist/visual-app-lib/types/subject-identity.d.ts +34 -0
- package/dist/visual-app-lib/types/visual-app/App.d.ts +27 -2
- package/dist/visual-app-lib/types/visual-app/badges.d.ts +1 -0
- package/dist/visual-app-lib/types/visual-app/connection-panel.d.ts +9 -1
- package/dist/visual-app-lib/types/visual-app/context-menu-model.d.ts +15 -6
- package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +77 -6
- package/dist/visual-app-lib/types/visual-app/kind-palette.d.ts +37 -0
- package/dist/visual-app-lib/types/visual-app/mount.d.ts +73 -2
- package/dist/visual-app-lib/types/visual-app/open-questions.d.ts +15 -0
- package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +3 -2
- package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +8 -1
- package/dist/visual-app-lib/types/visual-app/save-view.d.ts +11 -5
- package/dist/visual-app-lib/types/visual-app/shipped-catalogue.d.ts +10 -0
- package/dist/visual-app-lib/types/visual-app/state.d.ts +15 -4
- package/dist/visual-app-lib/types/visual-app/subject-draft-panel.d.ts +13 -1
- package/dist/visual-app-lib/types/visual-app/subject-filter.d.ts +35 -0
- package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +8 -0
- package/dist/visual-app-lib/types/visual-app/view-tree-model.d.ts +42 -9
- package/dist/visual-app-lib/types/visual-app/view-tree.d.ts +11 -2
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +81 -11
- package/docs/CONSUMING-YARRAMATE.md +40 -0
- package/package.json +1 -1
- package/schema/yarramate-design-step.schema.json +13 -0
- package/schema/yarramate-interrogation-report.schema.json +13 -0
- package/schema/yarramate-projection.schema.json +9 -0
- package/schema/yarramate-question-catalogue.schema.json +13 -0
- package/skills/yarramate-architecture/SKILL.md +5 -0
- package/dist/visual-app/assets/index-CcmfL3oY.js +0 -394
- package/dist/visual-app/assets/index-DLutIWES.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
format: yarramate/question-catalogue/v1
|
|
2
2
|
id: core-enrichment
|
|
3
|
-
version: "1.
|
|
3
|
+
version: "1.2"
|
|
4
4
|
profile: yarramate/core@0.1
|
|
5
5
|
presentation:
|
|
6
6
|
title: Core enrichment interview
|
|
@@ -880,6 +880,67 @@ questions:
|
|
|
880
880
|
Add the externally meaningful services and serve them to their
|
|
881
881
|
consumers.
|
|
882
882
|
|
|
883
|
+
- id: no-capability-declared
|
|
884
|
+
wave: business
|
|
885
|
+
since: "1.2"
|
|
886
|
+
scope: workspace
|
|
887
|
+
trigger:
|
|
888
|
+
- condition: no-subject-of-kind
|
|
889
|
+
kinds:
|
|
890
|
+
- yarramate/core@0.1#capability
|
|
891
|
+
question: >-
|
|
892
|
+
No capability is declared. What can this system do, in its own terms?
|
|
893
|
+
askPlain: >-
|
|
894
|
+
Forget the parts list for a moment: what are the handful of things
|
|
895
|
+
this system is actually able to do for the people it serves?
|
|
896
|
+
materiality: >-
|
|
897
|
+
Capabilities name what the system can do apart from how it currently
|
|
898
|
+
does it; without them, investment and sourcing trade-offs can only be
|
|
899
|
+
argued in component names, and nothing in the model can say which
|
|
900
|
+
parts serve the same ability. A subject-driven interview never asks
|
|
901
|
+
about a layer with zero subjects, so an absent capability map reads
|
|
902
|
+
as a covered one until this question opens it.
|
|
903
|
+
authority: human
|
|
904
|
+
resolution: >-
|
|
905
|
+
Add the small set of capabilities this system provides — four to
|
|
906
|
+
seven usually carries the whole conversation — and relate each to
|
|
907
|
+
the services or components that realize it with realization.
|
|
908
|
+
|
|
909
|
+
- id: no-contract-declared
|
|
910
|
+
wave: business
|
|
911
|
+
since: "1.2"
|
|
912
|
+
scope: workspace
|
|
913
|
+
trigger:
|
|
914
|
+
- condition: exists-linkage
|
|
915
|
+
kinds:
|
|
916
|
+
- yarramate/core@0.1#serving
|
|
917
|
+
- yarramate/core@0.1#flow
|
|
918
|
+
- yarramate/core@0.1#triggering
|
|
919
|
+
direction: either
|
|
920
|
+
counterpartKinds:
|
|
921
|
+
- yarramate/core@0.1#applicationComponent
|
|
922
|
+
- yarramate/core@0.1#applicationInterface
|
|
923
|
+
- yarramate/core@0.1#applicationService
|
|
924
|
+
- yarramate/core@0.1#businessActor
|
|
925
|
+
- condition: no-subject-of-kind
|
|
926
|
+
kinds:
|
|
927
|
+
- yarramate/core@0.1#contract
|
|
928
|
+
question: >-
|
|
929
|
+
Interactions exist and no contract governs any of them. Which
|
|
930
|
+
agreements bind what this architecture exchanges?
|
|
931
|
+
askPlain: >-
|
|
932
|
+
Things here talk to each other and to the outside. Where is it
|
|
933
|
+
written down what each side may expect — the API description, the
|
|
934
|
+
published schema, the service agreement?
|
|
935
|
+
materiality: >-
|
|
936
|
+
A hop with no contract is renegotiated by every implementer who
|
|
937
|
+
touches it; compatibility and versioning obligations exist only
|
|
938
|
+
where the agreement is a subject someone can change deliberately.
|
|
939
|
+
authority: human
|
|
940
|
+
resolution: >-
|
|
941
|
+
Add a contract for each formal agreement and access it from the
|
|
942
|
+
behavior bound by it; realization from the data object or artifact
|
|
943
|
+
that embodies the agreement says where it lives.
|
|
883
944
|
|
|
884
945
|
- id: service-realizes-no-motivation
|
|
885
946
|
wave: business
|
|
@@ -1063,6 +1124,38 @@ questions:
|
|
|
1063
1124
|
not perform application behavior: assign the actor to the business
|
|
1064
1125
|
process that uses it and let the application service serve that
|
|
1065
1126
|
process.
|
|
1127
|
+
- id: no-event-declared
|
|
1128
|
+
wave: application
|
|
1129
|
+
since: "1.2"
|
|
1130
|
+
scope: workspace
|
|
1131
|
+
trigger:
|
|
1132
|
+
- condition: no-subject-of-kind
|
|
1133
|
+
kinds:
|
|
1134
|
+
- yarramate/core@0.1#applicationEvent
|
|
1135
|
+
- yarramate/core@0.1#businessEvent
|
|
1136
|
+
question: >-
|
|
1137
|
+
Nothing in this model reacts to an event. Does state change only
|
|
1138
|
+
when something is called, or are there happenings the architecture
|
|
1139
|
+
responds to?
|
|
1140
|
+
askPlain: >-
|
|
1141
|
+
Is everything here a direct request, or do things also happen on
|
|
1142
|
+
their own — a message arrives, a job finishes, a threshold is
|
|
1143
|
+
crossed — that the system has to react to?
|
|
1144
|
+
materiality: >-
|
|
1145
|
+
Whether change propagates by call or by event decides coupling,
|
|
1146
|
+
ordering, and failure isolation. An event-driven seam modelled as
|
|
1147
|
+
silence leaves every consumer to discover the event stream on its
|
|
1148
|
+
own, and a repository full of event definitions the interview never
|
|
1149
|
+
asked about reads as covered when it is absent.
|
|
1150
|
+
authority: human
|
|
1151
|
+
resolution: >-
|
|
1152
|
+
Add the application or business events the system emits or responds
|
|
1153
|
+
to and wire each with triggering from what raises it to what
|
|
1154
|
+
responds. If there are genuinely none, that is a coupling decision
|
|
1155
|
+
worth stating in a principle or a service description; this question
|
|
1156
|
+
then stays on the agenda as the standing record that nobody has
|
|
1157
|
+
declared one.
|
|
1158
|
+
|
|
1066
1159
|
- id: event-triggers-nothing
|
|
1067
1160
|
wave: application
|
|
1068
1161
|
since: "0.3"
|
|
@@ -1232,6 +1325,31 @@ questions:
|
|
|
1232
1325
|
Add realization from the node, system software, or technology
|
|
1233
1326
|
behavior that provides the service.
|
|
1234
1327
|
|
|
1328
|
+
- id: no-artifact-declared
|
|
1329
|
+
wave: technology
|
|
1330
|
+
since: "1.2"
|
|
1331
|
+
scope: workspace
|
|
1332
|
+
trigger:
|
|
1333
|
+
- condition: no-subject-of-kind
|
|
1334
|
+
kinds:
|
|
1335
|
+
- yarramate/core@0.1#artifact
|
|
1336
|
+
question: >-
|
|
1337
|
+
No artifact is declared. What do the declared components actually
|
|
1338
|
+
ship as?
|
|
1339
|
+
askPlain: >-
|
|
1340
|
+
When this system is deployed, what is the thing that moves — an
|
|
1341
|
+
image, a package, a bundle? What are those called?
|
|
1342
|
+
materiality: >-
|
|
1343
|
+
Release engineering starts where a build output becomes a subject.
|
|
1344
|
+
An architecture with components but no artifacts can say what should
|
|
1345
|
+
exist and nothing about what is deployed, so drift between built and
|
|
1346
|
+
declared has nowhere to register.
|
|
1347
|
+
authority: human
|
|
1348
|
+
resolution: >-
|
|
1349
|
+
Add the artifacts the build produces, assignment from the node that
|
|
1350
|
+
deploys each one, and realization from the artifact to the component
|
|
1351
|
+
or data object it materializes.
|
|
1352
|
+
|
|
1235
1353
|
- id: artifact-unassigned
|
|
1236
1354
|
wave: technology
|
|
1237
1355
|
since: "0.4"
|
|
@@ -1258,6 +1376,37 @@ questions:
|
|
|
1258
1376
|
realization from the artifact to the component or data object it
|
|
1259
1377
|
materializes.
|
|
1260
1378
|
# ---- implementation ------------------------------------------------------
|
|
1379
|
+
- id: implementation-path-missing
|
|
1380
|
+
wave: implementation
|
|
1381
|
+
since: "1.2"
|
|
1382
|
+
scope: workspace
|
|
1383
|
+
trigger:
|
|
1384
|
+
- condition: no-subject-of-kind
|
|
1385
|
+
kinds:
|
|
1386
|
+
- yarramate/core@0.1#workPackage
|
|
1387
|
+
- yarramate/core@0.1#deliverable
|
|
1388
|
+
- yarramate/core@0.1#plateau
|
|
1389
|
+
question: >-
|
|
1390
|
+
No work package, deliverable, or plateau is declared. How does the
|
|
1391
|
+
planned architecture become real?
|
|
1392
|
+
askPlain: >-
|
|
1393
|
+
Who is doing what to get from here to there? What are the packages
|
|
1394
|
+
of work, and what does each one hand over?
|
|
1395
|
+
materiality: >-
|
|
1396
|
+
A model that names a target but no work reaching it is a wish with
|
|
1397
|
+
an architecture diagram; committed work packages and their
|
|
1398
|
+
deliverables are what make progress reviewable rather than
|
|
1399
|
+
reported. A repository mid-migration that models none of the
|
|
1400
|
+
migration is the sharpest form of the gap: the change is in the
|
|
1401
|
+
tree and invisible in the model.
|
|
1402
|
+
authority: human
|
|
1403
|
+
resolution: >-
|
|
1404
|
+
Add the work packages in flight, realization to the deliverables
|
|
1405
|
+
each produces, and plateaus where an intermediate state needs a
|
|
1406
|
+
name. An architecture genuinely at rest keeps this question open,
|
|
1407
|
+
which is itself information: the model is saying nothing is
|
|
1408
|
+
changing.
|
|
1409
|
+
|
|
1261
1410
|
- id: workpackage-delivers-nothing
|
|
1262
1411
|
wave: implementation
|
|
1263
1412
|
since: "0.4"
|
|
@@ -1426,6 +1575,36 @@ questions:
|
|
|
1426
1575
|
resolution: >-
|
|
1427
1576
|
Add a description claim stating meaning and explicit exclusions.
|
|
1428
1577
|
|
|
1578
|
+
- id: capability-uncited
|
|
1579
|
+
wave: hygiene
|
|
1580
|
+
since: "1.2"
|
|
1581
|
+
scope: subject
|
|
1582
|
+
subjects:
|
|
1583
|
+
kinds:
|
|
1584
|
+
- yarramate/core@0.1#capability
|
|
1585
|
+
trigger:
|
|
1586
|
+
- condition: missing-reference
|
|
1587
|
+
predicate: yarramate/reference/refers-to
|
|
1588
|
+
direction: outgoing
|
|
1589
|
+
question: >-
|
|
1590
|
+
{subject.name} cites nothing. Where is this ability specified,
|
|
1591
|
+
decided, or documented?
|
|
1592
|
+
askPlain: >-
|
|
1593
|
+
If someone asked where it says the system can do
|
|
1594
|
+
"{subject.name}", what would you point at?
|
|
1595
|
+
materiality: >-
|
|
1596
|
+
A capability with no citation floats free of every record that
|
|
1597
|
+
could confirm or correct it: an audit has nothing to grade the
|
|
1598
|
+
claim against, and when its scope is disputed the dispute starts
|
|
1599
|
+
from memory. The reference is where the model meets the
|
|
1600
|
+
repository's own account of itself.
|
|
1601
|
+
authority: either
|
|
1602
|
+
resolution: >-
|
|
1603
|
+
Add a references entry from the capability to the subject that
|
|
1604
|
+
specifies it — the decision record, the published schema, the
|
|
1605
|
+
guide that documents the journey. Model that record as a subject
|
|
1606
|
+
first where it is not one yet.
|
|
1607
|
+
|
|
1429
1608
|
- id: status-missing
|
|
1430
1609
|
wave: hygiene
|
|
1431
1610
|
since: "0.1"
|
|
@@ -1479,6 +1658,12 @@ questions:
|
|
|
1479
1658
|
genuinely different, say so in the model: add the counterpart's id to
|
|
1480
1659
|
this subject's distinctFrom list. That answer is itself a claim, so it
|
|
1481
1660
|
closes the question permanently and survives re-running the interview.
|
|
1661
|
+
A third answer is ordinary in a repository mid-migration: they are
|
|
1662
|
+
different subjects and one is taking over from the other. Record the
|
|
1663
|
+
succession with supersedes, scoped where the takeover is partial
|
|
1664
|
+
(ADR 0109), and record distinctFrom alongside it. Succession says what
|
|
1665
|
+
the pair is to each other and distinctness is what this question asks,
|
|
1666
|
+
so only the second closes it.
|
|
1482
1667
|
|
|
1483
1668
|
- id: states-undefined
|
|
1484
1669
|
wave: hygiene
|
|
@@ -1489,13 +1674,24 @@ questions:
|
|
|
1489
1674
|
question: >-
|
|
1490
1675
|
Does change shape matter here — should baseline, transition, or target
|
|
1491
1676
|
states be declared?
|
|
1677
|
+
askPlain: >-
|
|
1678
|
+
Are we describing how things are, how they should become, or both?
|
|
1679
|
+
If the repository already carries a migration plan or a target
|
|
1680
|
+
design, that document is the answer.
|
|
1492
1681
|
materiality: >-
|
|
1493
1682
|
Without architecture states, current and target intent share one
|
|
1494
|
-
undifferentiated model and comparisons are impossible.
|
|
1683
|
+
undifferentiated model and comparisons are impossible. The answer is
|
|
1684
|
+
often already on the record: a repository that carries its own
|
|
1685
|
+
migration plan or target design in-tree has declared that change
|
|
1686
|
+
shape matters, so evidence can bring this question its answer rather
|
|
1687
|
+
than waiting for someone to remember it.
|
|
1495
1688
|
authority: human
|
|
1496
1689
|
resolution: >-
|
|
1497
1690
|
Declare architecture states and mark presence with present-in where the
|
|
1498
1691
|
distinction carries decisions; explicitly decline states otherwise.
|
|
1692
|
+
Where in-tree documents already describe the target, model that
|
|
1693
|
+
record and cite it with references from the subjects it changes, so
|
|
1694
|
+
the declared states stand on evidence a reviewer can open.
|
|
1499
1695
|
|
|
1500
1696
|
- id: kind-untested
|
|
1501
1697
|
wave: hygiene
|
|
@@ -1580,3 +1776,30 @@ questions:
|
|
|
1580
1776
|
which says the same thing without a qualifier. If the two subjects
|
|
1581
1777
|
simply coexist and neither takes over from the other, the succession
|
|
1582
1778
|
is the wrong claim; remove it.
|
|
1779
|
+
|
|
1780
|
+
- id: evidence-unchallenged
|
|
1781
|
+
wave: hygiene
|
|
1782
|
+
since: "1.2"
|
|
1783
|
+
scope: workspace
|
|
1784
|
+
trigger:
|
|
1785
|
+
- condition: unchallenged-evidence
|
|
1786
|
+
question: >-
|
|
1787
|
+
Every observation this workspace records is a frictionless
|
|
1788
|
+
confirmation. Did the inspection ever test a claim it might fail?
|
|
1789
|
+
askPlain: >-
|
|
1790
|
+
The evidence agrees with the model everywhere. Did we ever look
|
|
1791
|
+
for something that might not be there — a documented piece missing
|
|
1792
|
+
from the tree, a declared dependency nothing carries?
|
|
1793
|
+
materiality: >-
|
|
1794
|
+
An overlay that only ever says confirmed is indistinguishable from
|
|
1795
|
+
one that only looked where success was guaranteed. One recorded
|
|
1796
|
+
search or one honest non-confirmation is what makes the agreement
|
|
1797
|
+
between model and reality worth believing; a reconcile summary of
|
|
1798
|
+
pure confirmations has never put a claim at risk.
|
|
1799
|
+
authority: either
|
|
1800
|
+
resolution: >-
|
|
1801
|
+
Probe at least one claim the sources assert that the tree might not
|
|
1802
|
+
honour, and record the observation with its honest result. A
|
|
1803
|
+
not-observed carries the searches that came back empty (ADR 0107);
|
|
1804
|
+
a confirmed negative claim carries them too, because the empty
|
|
1805
|
+
search is the confirmation. Either closes this.
|
|
@@ -109,6 +109,21 @@ const LIMIT_FREEZE = {
|
|
|
109
109
|
};
|
|
110
110
|
/** Built browser application, beside the compiled adapter in `dist`. */
|
|
111
111
|
const DEFAULT_ASSET_ROOT = fileURLToPath(new URL("../../visual-app/", import.meta.url));
|
|
112
|
+
/**
|
|
113
|
+
* The shipped question catalogue, read once per process for the model's
|
|
114
|
+
* interrogation overlay (#292). The same package-relative hop `design`
|
|
115
|
+
* makes; `undefined` when unreadable, which ships models with no overlay
|
|
116
|
+
* rather than failing the session over a garnish.
|
|
117
|
+
*/
|
|
118
|
+
const shippedCatalogue = (() => {
|
|
119
|
+
const path = fileURLToPath(new URL("../../../catalogues/core-enrichment.yaml", import.meta.url));
|
|
120
|
+
try {
|
|
121
|
+
return { path, source: readFileSync(path, "utf8") };
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
112
127
|
/**
|
|
113
128
|
* Takes one session terminal, whatever caused it: a reviewer's End, a child
|
|
114
129
|
* that failed, a browser that never came back, a cancelling main agent, or a
|
|
@@ -550,7 +565,7 @@ export const startVisualServer = async (options) => {
|
|
|
550
565
|
// that. A staged view operation pins against these (ADR 0103), and a
|
|
551
566
|
// projection missing from the map is one the commit will create.
|
|
552
567
|
projectionDigests: projectionDigestsNow(),
|
|
553
|
-
});
|
|
568
|
+
}, shippedCatalogue);
|
|
554
569
|
// Closures below retain this array, so refresh its contents without
|
|
555
570
|
// replacing the identity the session started with.
|
|
556
571
|
views.splice(0, views.length, ...workspaceModel.views);
|
|
@@ -59,7 +59,7 @@ export declare const declaredFolder: (view: {
|
|
|
59
59
|
*
|
|
60
60
|
* Every other presentation field the view declared is carried through by
|
|
61
61
|
* `composeProjection`, so a rename cannot quietly drop a nesting vocabulary or
|
|
62
|
-
*
|
|
62
|
+
* the direction the view runs.
|
|
63
63
|
*/
|
|
64
64
|
export declare const renameView: (view: SavedView, title: string) => {
|
|
65
65
|
readonly path: string;
|
|
@@ -71,7 +71,7 @@ export const declaredFolder = (view) => view.presentation?.folder ?? "";
|
|
|
71
71
|
*
|
|
72
72
|
* Every other presentation field the view declared is carried through by
|
|
73
73
|
* `composeProjection`, so a rename cannot quietly drop a nesting vocabulary or
|
|
74
|
-
*
|
|
74
|
+
* the direction the view runs.
|
|
75
75
|
*/
|
|
76
76
|
export const renameView = (view, title) => ({
|
|
77
77
|
path: view.path,
|
|
@@ -137,8 +137,9 @@ export const enumeratesSubjects = (query) => query.subjects !== undefined;
|
|
|
137
137
|
* it was is a row the reviewer has to read and discard for nothing.
|
|
138
138
|
*/
|
|
139
139
|
export const withMembership = (projection, subjectId, membership) => {
|
|
140
|
-
if (!enumeratesSubjects(projection.query))
|
|
141
|
-
return
|
|
140
|
+
if (!enumeratesSubjects(projection.query)) {
|
|
141
|
+
return withExclusion(projection, subjectId, membership);
|
|
142
|
+
}
|
|
142
143
|
const subjects = projection.query.subjects;
|
|
143
144
|
const holds = subjects.includes(subjectId);
|
|
144
145
|
if (membership === "add" ? holds : !holds)
|
|
@@ -155,6 +156,45 @@ export const withMembership = (projection, subjectId, membership) => {
|
|
|
155
156
|
},
|
|
156
157
|
};
|
|
157
158
|
};
|
|
159
|
+
/**
|
|
160
|
+
* The same faceted view, holding one more exception or one fewer (#267,
|
|
161
|
+
* ADR 0122).
|
|
162
|
+
*
|
|
163
|
+
* A view that describes its subjects with facets states a RULE, and there is
|
|
164
|
+
* no list to edit — which is why membership used to stop here. But every
|
|
165
|
+
* interesting rule has an exception, and `exclude` is where the author writes
|
|
166
|
+
* one down instead of abandoning the facet for a hand-enumerated list. So
|
|
167
|
+
* "remove from view" names the subject in `exclude`, and "add to this view"
|
|
168
|
+
* takes the name back out.
|
|
169
|
+
*
|
|
170
|
+
* The asymmetry is deliberate and is the whole reason there is no `include`
|
|
171
|
+
* tier here: taking an exception BACK is expressible, while adding a subject
|
|
172
|
+
* the facets do not select is not — that would silently convert the rule into
|
|
173
|
+
* a list. A subject that is neither excluded nor selected therefore gets
|
|
174
|
+
* `null`, the same answer this returns for every other no-op.
|
|
175
|
+
*
|
|
176
|
+
* `exclude` is a statement about the rule rather than about today's match set,
|
|
177
|
+
* so it is written whether or not the subject happens to be drawn: a model
|
|
178
|
+
* that later grows into the facet still honours the decision someone made.
|
|
179
|
+
*/
|
|
180
|
+
const withExclusion = (projection, subjectId, membership) => {
|
|
181
|
+
const excluded = projection.query.exclude ?? [];
|
|
182
|
+
const isExcluded = excluded.includes(subjectId);
|
|
183
|
+
if (membership === "add" ? !isExcluded : isExcluded)
|
|
184
|
+
return null;
|
|
185
|
+
const next = membership === "add"
|
|
186
|
+
? excluded.filter((id) => id !== subjectId)
|
|
187
|
+
: // Appended rather than sorted in, the rule the subjects list follows.
|
|
188
|
+
[...excluded, subjectId];
|
|
189
|
+
// The last exception leaving takes the key with it: `exclude: []` says the
|
|
190
|
+
// same thing as no key at all, and the schema's `minItems` refuses it
|
|
191
|
+
// anyway. Rebuilt rather than spread-over so the key is genuinely absent.
|
|
192
|
+
const { exclude: _dropped, ...rest } = projection.query;
|
|
193
|
+
return {
|
|
194
|
+
...projection,
|
|
195
|
+
query: next.length === 0 ? rest : { ...rest, exclude: next },
|
|
196
|
+
};
|
|
197
|
+
};
|
|
158
198
|
/**
|
|
159
199
|
* What a membership edit did to a view, as the tray reads it: `+id` for a
|
|
160
200
|
* subject this row adds, `-id` for one it drops, against the document the
|
|
@@ -162,6 +202,24 @@ export const withMembership = (projection, subjectId, membership) => {
|
|
|
162
202
|
* query, a presentation flag — which the row says by other means.
|
|
163
203
|
*/
|
|
164
204
|
export const membershipDelta = (saved, staged) => {
|
|
205
|
+
if (!enumeratesSubjects(saved) && !enumeratesSubjects(staged)) {
|
|
206
|
+
// A faceted view's membership moves through `exclude`, and reads inverted
|
|
207
|
+
// there: a name ARRIVING in the exclusion list is a subject leaving the
|
|
208
|
+
// view (#267). The tray says what happened to the view, not what happened
|
|
209
|
+
// to the list.
|
|
210
|
+
const savedExclusions = saved.exclude ?? [];
|
|
211
|
+
const stagedExclusions = staged.exclude ?? [];
|
|
212
|
+
return [
|
|
213
|
+
...savedExclusions
|
|
214
|
+
.filter((id) => !stagedExclusions.includes(id))
|
|
215
|
+
.map((id) => `+${id}`),
|
|
216
|
+
...stagedExclusions
|
|
217
|
+
.filter((id) => !savedExclusions.includes(id))
|
|
218
|
+
.map((id) => `-${id}`),
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
// One side enumerates and the other does not: the query changed shape rather
|
|
222
|
+
// than its membership, which the row says by other means.
|
|
165
223
|
if (!enumeratesSubjects(saved) || !enumeratesSubjects(staged))
|
|
166
224
|
return [];
|
|
167
225
|
return [
|
|
@@ -10,11 +10,41 @@ import type { VISUAL_PROTOCOL_VERSION, VisualApplyResultPayload, VisualAuthority
|
|
|
10
10
|
* that serves it — this module holds types only, and every dependency it has on
|
|
11
11
|
* the protocol is type-only too.
|
|
12
12
|
*/
|
|
13
|
+
/** One open question, as the question panel and a node's chip read it. */
|
|
14
|
+
export interface VisualQuestionEntry {
|
|
15
|
+
readonly questionId: string;
|
|
16
|
+
/** Rendered phrasing — per-subject questions arrive already interpolated. */
|
|
17
|
+
readonly question: string;
|
|
18
|
+
readonly authority: 'human' | 'agent' | 'either';
|
|
19
|
+
readonly since?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The interrogation report, folded for drawing (#292).
|
|
23
|
+
*
|
|
24
|
+
* Derived per successful recompile from the same compile the graph came
|
|
25
|
+
* from, and never stored — the stateless-interview rule as the canvas sees
|
|
26
|
+
* it. Optional on the model: a host that computes no overlay ships none,
|
|
27
|
+
* and the app draws no chips rather than zeros.
|
|
28
|
+
*/
|
|
29
|
+
export interface VisualInterrogationOverlay {
|
|
30
|
+
/** `id@version` of the catalogue that asked. */
|
|
31
|
+
readonly catalogue: string;
|
|
32
|
+
/** Engine condition-semantics version (ADR 0106), carried so a consumer
|
|
33
|
+
* can tell "the model moved" from "the engine moved". */
|
|
34
|
+
readonly semantics: string;
|
|
35
|
+
/** Workspace-scoped open questions — they name no subject, so they are
|
|
36
|
+
* shown when nothing is selected rather than pinned to a node. */
|
|
37
|
+
readonly workspace: readonly VisualQuestionEntry[];
|
|
38
|
+
/** Open questions per qualified subject id — `CanvasNode.id`'s space. */
|
|
39
|
+
readonly subjects: Readonly<Record<string, readonly VisualQuestionEntry[]>>;
|
|
40
|
+
}
|
|
13
41
|
/** The resolved graph a session renders, as the browser receives it. */
|
|
14
42
|
export interface VisualRenderedModel {
|
|
15
43
|
readonly authority: VisualAuthority;
|
|
16
44
|
readonly initialView: string;
|
|
17
45
|
readonly graph: CanvasGraph;
|
|
46
|
+
/** Present only when the host evaluated the catalogue for this compile. */
|
|
47
|
+
readonly interrogation?: VisualInterrogationOverlay;
|
|
18
48
|
/** Manifest-relative document paths — the add-concept target dropdown. */
|
|
19
49
|
readonly documents: readonly string[];
|
|
20
50
|
readonly vocabulary: {
|
|
@@ -5,7 +5,7 @@ import type { VisualDiagnostic, VisualViewOperation } from "./protocol-contract.
|
|
|
5
5
|
import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
6
6
|
import type { ResolvedProfileContext, SemanticGraph } from "../../compiler.js";
|
|
7
7
|
import type { VisualKindOption, VisualViewSummary } from "./protocol-contract.js";
|
|
8
|
-
import type { VisualRenderedModel } from "./wire.js";
|
|
8
|
+
import type { VisualInterrogationOverlay, VisualRenderedModel } from "./wire.js";
|
|
9
9
|
/**
|
|
10
10
|
* What a workspace looks like to the editor, however the editor is being run
|
|
11
11
|
* (#252).
|
|
@@ -41,17 +41,37 @@ export declare const kindOptionsOf: (lineages: ReadonlyMap<string, readonly stri
|
|
|
41
41
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
42
42
|
*/
|
|
43
43
|
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => number;
|
|
44
|
+
/**
|
|
45
|
+
* Folds one interrogation report into what the canvas draws (#292).
|
|
46
|
+
*
|
|
47
|
+
* Undefined — never a throw — when the catalogue does not load: the overlay
|
|
48
|
+
* is a garnish on the model, and a model frame must not be blocked by it.
|
|
49
|
+
* Subject ids come from the same compiled graph as `CanvasNode.id`, so the
|
|
50
|
+
* join in the browser is a plain lookup.
|
|
51
|
+
*/
|
|
52
|
+
export declare const interrogationOverlayOf: (compiled: {
|
|
53
|
+
readonly graph: SemanticGraph;
|
|
54
|
+
readonly profileContext: ResolvedProfileContext;
|
|
55
|
+
}, catalogue: {
|
|
56
|
+
readonly path: string;
|
|
57
|
+
readonly source: string;
|
|
58
|
+
}) => VisualInterrogationOverlay | undefined;
|
|
44
59
|
/**
|
|
45
60
|
* Rebuilds the shared editor workspace from one successful compile.
|
|
46
61
|
*
|
|
47
62
|
* The caller owns metadata which cannot be inferred from a graph (authority,
|
|
48
63
|
* source revisions, layouts, and initial view); this helper owns all derived
|
|
49
|
-
* canvas, vocabulary,
|
|
64
|
+
* canvas, vocabulary, view-count, and interrogation arithmetic. `catalogue`
|
|
65
|
+
* is the question catalogue's bytes — this module cannot read files, so
|
|
66
|
+
* whoever can hands them over; omitting it ships a model with no overlay.
|
|
50
67
|
*/
|
|
51
68
|
export declare const renderedWorkspaceOf: (compiled: {
|
|
52
69
|
readonly graph: SemanticGraph;
|
|
53
70
|
readonly profileContext: ResolvedProfileContext;
|
|
54
|
-
}, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary"
|
|
71
|
+
}, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary" | "interrogation">, catalogue?: {
|
|
72
|
+
readonly path: string;
|
|
73
|
+
readonly source: string;
|
|
74
|
+
}) => {
|
|
55
75
|
readonly model: VisualRenderedModel;
|
|
56
76
|
readonly views: readonly VisualViewSummary[];
|
|
57
77
|
};
|
|
@@ -6,6 +6,7 @@ import { projectGraphForCanvas } from "../../graph-projection.js";
|
|
|
6
6
|
import { DEFAULT_PROJECTION_DIRECTORY } from "./view-identity.js";
|
|
7
7
|
import { evaluateProjection, explainProjection } from "../../projection.js";
|
|
8
8
|
import { kindLabelOf } from "../../kind-label.js";
|
|
9
|
+
import { evaluateCatalogue, loadQuestionCatalogue, } from "../../interrogate-command.js";
|
|
9
10
|
/**
|
|
10
11
|
* What a workspace looks like to the editor, however the editor is being run
|
|
11
12
|
* (#252).
|
|
@@ -45,18 +46,66 @@ export const kindOptionsOf = (lineages) => [...lineages.keys()].map((id) => ({
|
|
|
45
46
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
46
47
|
*/
|
|
47
48
|
export const conceptCountOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.filter(({ type }) => type === "concept").length;
|
|
49
|
+
/**
|
|
50
|
+
* Folds one interrogation report into what the canvas draws (#292).
|
|
51
|
+
*
|
|
52
|
+
* Undefined — never a throw — when the catalogue does not load: the overlay
|
|
53
|
+
* is a garnish on the model, and a model frame must not be blocked by it.
|
|
54
|
+
* Subject ids come from the same compiled graph as `CanvasNode.id`, so the
|
|
55
|
+
* join in the browser is a plain lookup.
|
|
56
|
+
*/
|
|
57
|
+
export const interrogationOverlayOf = (compiled, catalogue) => {
|
|
58
|
+
const loaded = loadQuestionCatalogue(catalogue);
|
|
59
|
+
if (!loaded.ok)
|
|
60
|
+
return undefined;
|
|
61
|
+
const report = evaluateCatalogue(loaded.catalogue, compiled.graph, compiled.profileContext);
|
|
62
|
+
const workspace = [];
|
|
63
|
+
const subjects = {};
|
|
64
|
+
for (const wave of report.waves) {
|
|
65
|
+
for (const question of wave.questions) {
|
|
66
|
+
if (!question.open)
|
|
67
|
+
continue;
|
|
68
|
+
const base = {
|
|
69
|
+
questionId: question.id,
|
|
70
|
+
authority: question.authority,
|
|
71
|
+
...(question.since === undefined ? {} : { since: question.since }),
|
|
72
|
+
};
|
|
73
|
+
if (question.subjects === undefined) {
|
|
74
|
+
workspace.push({ ...base, question: question.question });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
for (const subject of question.subjects) {
|
|
78
|
+
(subjects[subject.id] ??= []).push({
|
|
79
|
+
...base,
|
|
80
|
+
question: subject.question,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
catalogue: report.catalogue,
|
|
87
|
+
semantics: report.semantics,
|
|
88
|
+
workspace,
|
|
89
|
+
subjects,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
48
92
|
/**
|
|
49
93
|
* Rebuilds the shared editor workspace from one successful compile.
|
|
50
94
|
*
|
|
51
95
|
* The caller owns metadata which cannot be inferred from a graph (authority,
|
|
52
96
|
* source revisions, layouts, and initial view); this helper owns all derived
|
|
53
|
-
* canvas, vocabulary,
|
|
97
|
+
* canvas, vocabulary, view-count, and interrogation arithmetic. `catalogue`
|
|
98
|
+
* is the question catalogue's bytes — this module cannot read files, so
|
|
99
|
+
* whoever can hands them over; omitting it ships a model with no overlay.
|
|
54
100
|
*/
|
|
55
|
-
export const renderedWorkspaceOf = (compiled, views, metadata) => {
|
|
101
|
+
export const renderedWorkspaceOf = (compiled, views, metadata, catalogue) => {
|
|
56
102
|
const refreshedViews = views.map((view) => ({
|
|
57
103
|
...view,
|
|
58
104
|
subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext),
|
|
59
105
|
}));
|
|
106
|
+
const interrogation = catalogue === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: interrogationOverlayOf(compiled, catalogue);
|
|
60
109
|
return {
|
|
61
110
|
model: {
|
|
62
111
|
...metadata,
|
|
@@ -65,6 +114,7 @@ export const renderedWorkspaceOf = (compiled, views, metadata) => {
|
|
|
65
114
|
conceptKinds: kindOptionsOf(compiled.profileContext.conceptKindLineages),
|
|
66
115
|
relationshipKinds: kindOptionsOf(compiled.profileContext.relationshipKindLineages),
|
|
67
116
|
},
|
|
117
|
+
...(interrogation === undefined ? {} : { interrogation }),
|
|
68
118
|
},
|
|
69
119
|
views: refreshedViews,
|
|
70
120
|
};
|
package/dist/ask-command.js
CHANGED
|
@@ -470,7 +470,7 @@ export function runAskCommand(options, cwd) {
|
|
|
470
470
|
});
|
|
471
471
|
if (!loadedCatalogue.ok)
|
|
472
472
|
return failed(loadedCatalogue.diagnostics);
|
|
473
|
-
const report = evaluateCatalogue(loadedCatalogue.catalogue, compilation.graph, compilation.profileContext);
|
|
473
|
+
const report = evaluateCatalogue(loadedCatalogue.catalogue, compilation.graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations));
|
|
474
474
|
const result = {
|
|
475
475
|
format: 'yarramate/ask-result/v1',
|
|
476
476
|
workspace: workspace.id,
|
|
@@ -689,8 +689,21 @@ export function runAskCommand(options, cwd) {
|
|
|
689
689
|
});
|
|
690
690
|
if (!loadedCatalogue.ok)
|
|
691
691
|
return failed(loadedCatalogue.diagnostics);
|
|
692
|
+
// The evidence overlay rides along for the one condition that
|
|
693
|
+
// reads it (unchallenged-evidence); a workspace declaring no
|
|
694
|
+
// evidence passes an overlay known to be empty.
|
|
695
|
+
const evidenceObservations = [];
|
|
696
|
+
for (const path of workspace.evidence) {
|
|
697
|
+
const loaded = loadEvidence({
|
|
698
|
+
path,
|
|
699
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
700
|
+
});
|
|
701
|
+
if (!loaded.ok)
|
|
702
|
+
return failed(loaded.diagnostics);
|
|
703
|
+
evidenceObservations.push(...loaded.evidence.observations);
|
|
704
|
+
}
|
|
692
705
|
const report = {
|
|
693
|
-
...evaluateCatalogue(loadedCatalogue.catalogue, graph, compilation.profileContext),
|
|
706
|
+
...evaluateCatalogue(loadedCatalogue.catalogue, graph, compilation.profileContext, evidenceObservations),
|
|
694
707
|
workspace: workspace.id,
|
|
695
708
|
};
|
|
696
709
|
const ordered = {
|
|
@@ -971,7 +984,20 @@ export function runAskCommand(options, cwd) {
|
|
|
971
984
|
});
|
|
972
985
|
if (!loadedCatalogue.ok)
|
|
973
986
|
return failed(loadedCatalogue.diagnostics);
|
|
974
|
-
|
|
987
|
+
// Loaded ahead of evaluation so the overlay feeds the one condition
|
|
988
|
+
// that reads it (unchallenged-evidence), then reused for the
|
|
989
|
+
// reconciliation summary below.
|
|
990
|
+
const evidenceDocuments = [];
|
|
991
|
+
for (const path of workspace.evidence) {
|
|
992
|
+
const loaded = loadEvidence({
|
|
993
|
+
path,
|
|
994
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
995
|
+
});
|
|
996
|
+
if (!loaded.ok)
|
|
997
|
+
return failed(loaded.diagnostics);
|
|
998
|
+
evidenceDocuments.push(loaded.evidence);
|
|
999
|
+
}
|
|
1000
|
+
const report = evaluateCatalogue(loadedCatalogue.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations));
|
|
975
1001
|
const openQuestions = [];
|
|
976
1002
|
for (const wave of report.waves) {
|
|
977
1003
|
for (const question of wave.questions) {
|
|
@@ -1003,16 +1029,6 @@ export function runAskCommand(options, cwd) {
|
|
|
1003
1029
|
}
|
|
1004
1030
|
let reconciliation;
|
|
1005
1031
|
if (workspace.evidence.length > 0) {
|
|
1006
|
-
const evidenceDocuments = [];
|
|
1007
|
-
for (const path of workspace.evidence) {
|
|
1008
|
-
const loaded = loadEvidence({
|
|
1009
|
-
path,
|
|
1010
|
-
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
1011
|
-
});
|
|
1012
|
-
if (!loaded.ok)
|
|
1013
|
-
return failed(loaded.diagnostics);
|
|
1014
|
-
evidenceDocuments.push(loaded.evidence);
|
|
1015
|
-
}
|
|
1016
1032
|
const evaluation = evaluateEvidenceWorkspace(graph, evidenceDocuments);
|
|
1017
1033
|
if (!evaluation.ok)
|
|
1018
1034
|
return failed(evaluation.diagnostics);
|