yarramate 1.34.0 → 1.36.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 +4 -18
- package/dist/adapters/visual/protocol-contract.d.ts +10 -0
- package/dist/adapters/visual/session-server.js +8 -6
- package/dist/adapters/visual/workspace-model.d.ts +3 -3
- package/dist/adapters/visual/workspace-model.js +27 -15
- package/dist/ask-command.js +6 -0
- package/dist/governance.js +11 -2
- package/dist/host/local-host.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/projection.js +16 -3
- package/dist/relationship-convention.d.ts +25 -0
- package/dist/relationship-convention.js +176 -0
- package/dist/shipped-catalogue.generated.d.ts +1 -1
- package/dist/shipped-catalogue.generated.js +1 -1
- package/dist/tools/ask.d.ts +41 -1
- package/dist/tools/ask.js +28 -2
- package/dist/tools/design.js +2 -0
- package/dist/tools/export.js +2 -0
- package/dist/tools-entry.d.ts +1 -1
- package/dist/tools-entry.js +1 -1
- package/dist/visual-app/assets/{elk.bundled-CFiHtVQt.js → elk.bundled-B_x2vU_0.js} +1 -1
- package/dist/visual-app/assets/{index-BaQ0vy3y.js → index-BRduex3u.js} +2 -2
- package/dist/visual-app/assets/index-Oc1hphkX.css +1 -0
- package/dist/visual-app/index.html +2 -2
- package/dist/visual-app-lib/editor.js +21490 -21386
- package/dist/visual-app-lib/styles.css +1 -1
- package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +10 -0
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +3 -3
- package/dist/visual-app-lib/types/relationship-convention.d.ts +25 -0
- package/dist/visual-app-lib/types/shipped-catalogue.generated.d.ts +1 -1
- package/dist/visual-app-lib/types/visual-app/relationship-kind-options.d.ts +8 -0
- package/dist/visual-app-lib/types/visual-app/session-client.d.ts +7 -1
- package/dist/visual-app-lib/types/visual-app/state.d.ts +1 -0
- package/package.json +1 -1
- package/schema/yarramate-ask-result.schema.json +13 -0
- package/schema/yarramate-visual-event.schema.json +4 -0
- package/skills/yarramate-architecture/references/native-authoring.md +8 -0
- package/dist/visual-app/assets/index-DYTKTQsG.css +0 -1
|
@@ -907,9 +907,6 @@ questions:
|
|
|
907
907
|
subjects:
|
|
908
908
|
kinds:
|
|
909
909
|
- yarramate/policy@0.3#risk
|
|
910
|
-
statuses:
|
|
911
|
-
- planned
|
|
912
|
-
- current
|
|
913
910
|
trigger:
|
|
914
911
|
- condition: missing-linkage
|
|
915
912
|
kinds:
|
|
@@ -939,8 +936,6 @@ questions:
|
|
|
939
936
|
subjects:
|
|
940
937
|
kinds:
|
|
941
938
|
- yarramate/policy@0.3#risk
|
|
942
|
-
statuses:
|
|
943
|
-
- current
|
|
944
939
|
trigger:
|
|
945
940
|
- condition: missing-linkage
|
|
946
941
|
kinds:
|
|
@@ -955,11 +950,11 @@ questions:
|
|
|
955
950
|
question: >-
|
|
956
951
|
What mitigates {subject.name}?
|
|
957
952
|
askPlain: >-
|
|
958
|
-
"{subject.name}" is
|
|
959
|
-
place to reduce it?
|
|
953
|
+
"{subject.name}" is on the log. What work, deliverable, rule or decision
|
|
954
|
+
is in place, or planned, to reduce it?
|
|
960
955
|
materiality: >-
|
|
961
|
-
A
|
|
962
|
-
saying so; the log exists to make that visible.
|
|
956
|
+
A risk on the log with no mitigation is a bet the engagement is making
|
|
957
|
+
without saying so; the log exists to make that visible.
|
|
963
958
|
authority: either
|
|
964
959
|
resolution: >-
|
|
965
960
|
Add an `influence` relationship from the work package, deliverable,
|
|
@@ -971,9 +966,6 @@ questions:
|
|
|
971
966
|
subjects:
|
|
972
967
|
kinds:
|
|
973
968
|
- yarramate/policy@0.3#risk
|
|
974
|
-
statuses:
|
|
975
|
-
- planned
|
|
976
|
-
- current
|
|
977
969
|
trigger:
|
|
978
970
|
- condition: missing-claim
|
|
979
971
|
predicate: yarramate/ownership/owner
|
|
@@ -995,9 +987,6 @@ questions:
|
|
|
995
987
|
subjects:
|
|
996
988
|
kinds:
|
|
997
989
|
- yarramate/policy@0.3#assumption
|
|
998
|
-
statuses:
|
|
999
|
-
- planned
|
|
1000
|
-
- current
|
|
1001
990
|
trigger:
|
|
1002
991
|
- condition: missing-attestation
|
|
1003
992
|
topic: assumption-confirmed
|
|
@@ -1021,9 +1010,6 @@ questions:
|
|
|
1021
1010
|
subjects:
|
|
1022
1011
|
kinds:
|
|
1023
1012
|
- yarramate/policy@0.3#assumption
|
|
1024
|
-
statuses:
|
|
1025
|
-
- planned
|
|
1026
|
-
- current
|
|
1027
1013
|
trigger:
|
|
1028
1014
|
- condition: missing-relationship
|
|
1029
1015
|
kinds:
|
|
@@ -255,6 +255,16 @@ export interface VisualFilterQueryPayload {
|
|
|
255
255
|
* exactly as it did before.
|
|
256
256
|
*/
|
|
257
257
|
readonly nesting?: readonly NestingKind[];
|
|
258
|
+
/**
|
|
259
|
+
* Whether the view shows responsibility edges (#563, ADR 0161).
|
|
260
|
+
*
|
|
261
|
+
* Only the `connected` walk reads it: a responsibility edge the canvas hides
|
|
262
|
+
* must not bring a person into the picture, and one the canvas shows must.
|
|
263
|
+
* Optional, so an older browser and every filter that does not send it
|
|
264
|
+
* evaluate as a view with the flag off, which is what the canvas draws by
|
|
265
|
+
* default.
|
|
266
|
+
*/
|
|
267
|
+
readonly showResponsibility?: boolean;
|
|
258
268
|
}
|
|
259
269
|
export interface VisualFilterResultPayload {
|
|
260
270
|
readonly query: ProjectionQuery;
|
|
@@ -680,7 +680,7 @@ export const startVisualServer = async (options) => {
|
|
|
680
680
|
if (!started.ok)
|
|
681
681
|
standingDiagnostics = started.diagnostics;
|
|
682
682
|
}
|
|
683
|
-
const filterMatchedIds = (query, nesting) => compiledWorkspace === undefined
|
|
683
|
+
const filterMatchedIds = (query, nesting, showResponsibility) => compiledWorkspace === undefined
|
|
684
684
|
? []
|
|
685
685
|
: matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext,
|
|
686
686
|
// A view's query can name `instances`, and the facet resolves to the
|
|
@@ -688,7 +688,9 @@ export const startVisualServer = async (options) => {
|
|
|
688
688
|
compiledWorkspace.patternMemberships,
|
|
689
689
|
// And it resolves the WRONG closure without the nesting the canvas is
|
|
690
690
|
// drawing with, which is a wrong number rather than a missing one.
|
|
691
|
-
nesting
|
|
691
|
+
nesting,
|
|
692
|
+
// And the walk reads the responsibility flag (#563).
|
|
693
|
+
showResponsibility);
|
|
692
694
|
/**
|
|
693
695
|
* Why a query dropped what it dropped, as the editor's "excluded, and why"
|
|
694
696
|
* list reads it (#248).
|
|
@@ -700,9 +702,9 @@ export const startVisualServer = async (options) => {
|
|
|
700
702
|
* the reason the editor shows and the set the canvas draws cannot come from
|
|
701
703
|
* two readings of the same query.
|
|
702
704
|
*/
|
|
703
|
-
const filterExclusions = (query, nesting) => compiledWorkspace === undefined
|
|
705
|
+
const filterExclusions = (query, nesting, showResponsibility) => compiledWorkspace === undefined
|
|
704
706
|
? []
|
|
705
|
-
: exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext, compiledWorkspace.patternMemberships, nesting);
|
|
707
|
+
: exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext, compiledWorkspace.patternMemberships, nesting, showResponsibility);
|
|
706
708
|
let listening = false;
|
|
707
709
|
let bootstrapSpent = false;
|
|
708
710
|
let agentAttached = false;
|
|
@@ -1255,8 +1257,8 @@ export const startVisualServer = async (options) => {
|
|
|
1255
1257
|
kind: "filter-result",
|
|
1256
1258
|
result: {
|
|
1257
1259
|
query: event.payload.query,
|
|
1258
|
-
matchedIds: filterMatchedIds(event.payload.query, event.payload.nesting),
|
|
1259
|
-
excluded: filterExclusions(event.payload.query, event.payload.nesting),
|
|
1260
|
+
matchedIds: filterMatchedIds(event.payload.query, event.payload.nesting, event.payload.showResponsibility),
|
|
1261
|
+
excluded: filterExclusions(event.payload.query, event.payload.nesting, event.payload.showResponsibility),
|
|
1260
1262
|
},
|
|
1261
1263
|
});
|
|
1262
1264
|
return;
|
|
@@ -57,7 +57,7 @@ export declare const patternOptionsOf: (patterns: readonly PatternShape[], linea
|
|
|
57
57
|
* subject count. A view over three components with two relationships between
|
|
58
58
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
59
59
|
*/
|
|
60
|
-
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => number;
|
|
60
|
+
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[], showResponsibility?: boolean) => number;
|
|
61
61
|
/**
|
|
62
62
|
* Folds one interrogation report into what the canvas draws (#292).
|
|
63
63
|
*
|
|
@@ -125,9 +125,9 @@ export declare const renderedWorkspaceOf: (compiled: {
|
|
|
125
125
|
readonly views: readonly VisualViewSummary[];
|
|
126
126
|
};
|
|
127
127
|
/** Every subject a query draws, concepts and relationships alike. */
|
|
128
|
-
export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly string[];
|
|
128
|
+
export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[], showResponsibility?: boolean) => readonly string[];
|
|
129
129
|
/** Every concept a query dropped, and the facet that dropped it (#248). */
|
|
130
|
-
export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly ProjectionExclusion[];
|
|
130
|
+
export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[], showResponsibility?: boolean) => readonly ProjectionExclusion[];
|
|
131
131
|
/**
|
|
132
132
|
* One saved view, as the rail reads it. `subjectCount` is the caller's,
|
|
133
133
|
* because counting needs a compiled graph and a session builds its first list
|
|
@@ -104,7 +104,7 @@ const wiringOf = (pattern, slot) => {
|
|
|
104
104
|
* subject count. A view over three components with two relationships between
|
|
105
105
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
106
106
|
*/
|
|
107
|
-
export const conceptCountOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.filter(({ type }) => type === "concept").length;
|
|
107
|
+
export const conceptCountOf = (graph, query, profileContext, memberships, nesting, showResponsibility) => evaluateProjection(graph, adHoc(query, nesting, showResponsibility), profileContext, memberships).subjects.filter(({ type }) => type === "concept").length;
|
|
108
108
|
export const interrogationOverlayOf = (compiled,
|
|
109
109
|
/**
|
|
110
110
|
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
@@ -209,7 +209,10 @@ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismis
|
|
|
209
209
|
// Each view's OWN nesting, because each view's closure is its own. A rail
|
|
210
210
|
// sitting beside the canvas must not count a different tree than the
|
|
211
211
|
// canvas draws.
|
|
212
|
-
view.presentation?.nesting
|
|
212
|
+
view.presentation?.nesting,
|
|
213
|
+
// And each view's own responsibility flag, for the same reason: the
|
|
214
|
+
// walk reads it (#563), and the rail must count what the canvas draws.
|
|
215
|
+
view.presentation?.showResponsibility),
|
|
213
216
|
}));
|
|
214
217
|
const interrogation = catalogue === undefined
|
|
215
218
|
? undefined
|
|
@@ -250,24 +253,33 @@ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismis
|
|
|
250
253
|
};
|
|
251
254
|
};
|
|
252
255
|
/** Every subject a query draws, concepts and relationships alike. */
|
|
253
|
-
export const matchedIdsOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.map(({ id }) => id);
|
|
256
|
+
export const matchedIdsOf = (graph, query, profileContext, memberships, nesting, showResponsibility) => evaluateProjection(graph, adHoc(query, nesting, showResponsibility), profileContext, memberships).subjects.map(({ id }) => id);
|
|
254
257
|
/** Every concept a query dropped, and the facet that dropped it (#248). */
|
|
255
|
-
export const exclusionsOf = (graph, query, profileContext, memberships, nesting) => explainProjection(graph, adHoc(query, nesting), profileContext, memberships);
|
|
258
|
+
export const exclusionsOf = (graph, query, profileContext, memberships, nesting, showResponsibility) => explainProjection(graph, adHoc(query, nesting, showResponsibility), profileContext, memberships);
|
|
256
259
|
/**
|
|
257
260
|
* A query on its own is not a projection, and every evaluator here wants one.
|
|
258
261
|
* The id is a placeholder that never reaches a document.
|
|
259
262
|
*/
|
|
260
|
-
const adHoc = (query, nesting) =>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
})
|
|
263
|
+
const adHoc = (query, nesting, showResponsibility) => {
|
|
264
|
+
const presentation = {
|
|
265
|
+
// `query.instances` resolves its closure through the view's nesting, so an
|
|
266
|
+
// ad-hoc projection that dropped the nesting would answer a DIFFERENT
|
|
267
|
+
// question than the canvas is drawing: 2 subjects against 15 on the
|
|
268
|
+
// ApertureX reference, with nothing to say it had (#473 phase 2).
|
|
269
|
+
...(nesting === undefined ? {} : { nesting }),
|
|
270
|
+
// And the `connected` walk reads this flag (#563, ADR 0161): dropped, the
|
|
271
|
+
// evaluation would walk a responsibility edge the canvas hides and stand a
|
|
272
|
+
// person in the picture with no line to anything.
|
|
273
|
+
...(showResponsibility === undefined ? {} : { showResponsibility }),
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
format: "yarramate/projection/v1",
|
|
277
|
+
id: "ad-hoc",
|
|
278
|
+
version: "0",
|
|
279
|
+
query,
|
|
280
|
+
...(Object.keys(presentation).length === 0 ? {} : { presentation }),
|
|
281
|
+
};
|
|
282
|
+
};
|
|
271
283
|
/**
|
|
272
284
|
* One saved view, as the rail reads it. `subjectCount` is the caller's,
|
|
273
285
|
* because counting needs a compiled graph and a session builds its first list
|
package/dist/ask-command.js
CHANGED
|
@@ -605,6 +605,12 @@ export function runAskCommand(options, cwd) {
|
|
|
605
605
|
topic,
|
|
606
606
|
seeds: resolution.seeds,
|
|
607
607
|
matched: resolution.matched,
|
|
608
|
+
// The same document the tools entry emits, field for field (ADR 0156),
|
|
609
|
+
// and `test/tools-entry.test.ts` compares them. The ranked candidates
|
|
610
|
+
// ride on both (#569).
|
|
611
|
+
...(resolution.addressing === 'free-text'
|
|
612
|
+
? { candidates: resolution.candidates }
|
|
613
|
+
: {}),
|
|
608
614
|
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
609
615
|
result: evaluated,
|
|
610
616
|
};
|
package/dist/governance.js
CHANGED
|
@@ -122,7 +122,16 @@ export function buildGovernanceLog(workspace, graph, profileContext, options = {
|
|
|
122
122
|
});
|
|
123
123
|
const ids = (predicate) => rows.filter(predicate).map(({ subject }) => subject);
|
|
124
124
|
const unowned = ids((row) => row.owner === null);
|
|
125
|
-
const unmitigated = ids((row) =>
|
|
125
|
+
const unmitigated = ids((row) =>
|
|
126
|
+
// Every risk still open, whatever its status says or does not say. The
|
|
127
|
+
// first cut counted only `status: current`, so a risk nobody had given a
|
|
128
|
+
// status, which is every freshly authored one, was never here and the
|
|
129
|
+
// tile read 0 over a log where every risk was unmitigated (#564). The
|
|
130
|
+
// engine's word for "open" is not-retired: that is what keeps unstatused
|
|
131
|
+
// subjects in a projection with `excludeStatuses: [retired]` and what
|
|
132
|
+
// the interrogation applies by default. A planned risk is asked for its
|
|
133
|
+
// mitigation too; a mitigation can be planned.
|
|
134
|
+
row.type === 'risk' && row.status !== 'retired' && row.mitigatedBy.length === 0);
|
|
126
135
|
const unconfirmed = ids((row) => row.type === 'assumption' && row.review === null);
|
|
127
136
|
const unreviewed = ids((row) => row.type === 'risk' && row.review === null);
|
|
128
137
|
return {
|
|
@@ -159,6 +168,6 @@ export function renderGovernanceMarkdown(log) {
|
|
|
159
168
|
}
|
|
160
169
|
lines.push('');
|
|
161
170
|
}
|
|
162
|
-
lines.push('## Gaps', '', `- Unowned: ${list(log.gaps.unowned)}`, `- Unmitigated
|
|
171
|
+
lines.push('## Gaps', '', `- Unowned: ${list(log.gaps.unowned)}`, `- Unmitigated risks: ${list(log.gaps.unmitigated)}`, `- Unconfirmed assumptions: ${list(log.gaps.unconfirmed)}`, `- Unreviewed risks: ${list(log.gaps.unreviewed)}`, '');
|
|
163
172
|
return `${lines.join('\n')}\n`;
|
|
164
173
|
}
|
package/dist/host/local-host.js
CHANGED
|
@@ -384,8 +384,8 @@ export const createLocalHost = (options) => {
|
|
|
384
384
|
kind: 'filter-result',
|
|
385
385
|
result: {
|
|
386
386
|
query: input.payload.query,
|
|
387
|
-
matchedIds: matchedIdsOf(compiled.graph, input.payload.query, compiled.profileContext, compiled.patternMemberships, input.payload.nesting),
|
|
388
|
-
excluded: exclusionsOf(compiled.graph, input.payload.query, compiled.profileContext, compiled.patternMemberships, input.payload.nesting),
|
|
387
|
+
matchedIds: matchedIdsOf(compiled.graph, input.payload.query, compiled.profileContext, compiled.patternMemberships, input.payload.nesting, input.payload.showResponsibility),
|
|
388
|
+
excluded: exclusionsOf(compiled.graph, input.payload.query, compiled.profileContext, compiled.patternMemberships, input.payload.nesting, input.payload.showResponsibility),
|
|
389
389
|
},
|
|
390
390
|
});
|
|
391
391
|
return;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type { LifecycleStatus, ProjectionDefinition, ProjectionLoadResult, Proje
|
|
|
21
21
|
export { createFileSystemStore, type PendingWrite, type SourceStore, type StoredSource, type WriteConflict, type WriteOutcome, } from './source-store.js';
|
|
22
22
|
export { applyOperations, landOperations, posixDirectoryOf, type ApplyInput, type ApplyOutcome, } from './apply-command.js';
|
|
23
23
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
24
|
+
export { conventionalRelationshipKind, CONVENTION_RULES, type RelationshipConvention, } from './relationship-convention.js';
|
|
24
25
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
25
26
|
export { deletionBlockers, describeDeletion, draftDeletion, type DeletionBlocker, } from './deletion-drafting.js';
|
|
26
27
|
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, conditionInput, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CataloguePatternMembership, type CataloguePatternVacancy, type CatalogueInput, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, }
|
|
|
19
19
|
export { createFileSystemStore, } from './source-store.js';
|
|
20
20
|
export { applyOperations, landOperations, posixDirectoryOf, } from './apply-command.js';
|
|
21
21
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
22
|
+
export { conventionalRelationshipKind, CONVENTION_RULES, } from './relationship-convention.js';
|
|
22
23
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
23
24
|
export { deletionBlockers, describeDeletion, draftDeletion, } from './deletion-drafting.js';
|
|
24
25
|
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, conditionInput, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, } from './interrogate-command.js';
|
package/dist/projection.js
CHANGED
|
@@ -18,6 +18,7 @@ export { DEFAULT_FOLD } from './fold-tree.js';
|
|
|
18
18
|
import { DEFAULT_NESTING as NESTING_DEFAULT } from './nesting.js';
|
|
19
19
|
import { foldTree } from './fold-tree.js';
|
|
20
20
|
import { kindLabelOf } from './kind-label.js';
|
|
21
|
+
import { responsibilityLetterOf } from './responsibility-kinds.js';
|
|
21
22
|
/**
|
|
22
23
|
* Which way a view runs, and the default. Split out for the same reason as the
|
|
23
24
|
* nesting vocabulary above, and re-exported here on the same terms (ADR 0121).
|
|
@@ -563,10 +564,22 @@ export function evaluateProjection(graph, projection, profileContext, membership
|
|
|
563
564
|
endpointExcluded(relationship.object.ref)) {
|
|
564
565
|
continue;
|
|
565
566
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
567
|
+
const bothSelected = sourceSelected && targetSelected;
|
|
568
|
+
// A responsibility edge does not carry the `connected` walk unless the
|
|
569
|
+
// view shows responsibility (#563, ADR 0161). The canvas hides these
|
|
570
|
+
// edges while the flag is off (ADR 0159), so walking one stood a person
|
|
571
|
+
// in an application landscape with no line to anything: four of them on
|
|
572
|
+
// the ApertureX reference. Between two subjects the query selected on
|
|
573
|
+
// their own merits the edge is selected like any other, so a
|
|
574
|
+
// whole-workspace evaluation, the workbook and the matrix builders keep
|
|
575
|
+
// seeing every relationship; only the walk reads the flag. Read through
|
|
576
|
+
// the lineage, so an adopter's subkind of `responsible` is held back
|
|
577
|
+
// exactly as the shipped kind is.
|
|
578
|
+
const carriesTheWalk = projection.presentation?.showResponsibility === true ||
|
|
579
|
+
responsibilityLetterOf(profileContext?.relationshipKindLineages.get(relationship.predicate), relationship.predicate) === null;
|
|
580
|
+
if (bothSelected ||
|
|
569
581
|
(relationshipMode === 'connected' &&
|
|
582
|
+
carriesTheWalk &&
|
|
570
583
|
(sourceSelected || targetSelected))) {
|
|
571
584
|
selectedRelationshipIds.add(subject.id);
|
|
572
585
|
if (relationshipMode === 'connected') {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type RelationshipKind } from './profile.js';
|
|
2
|
+
/**
|
|
3
|
+
* One convention, as a named rule. The name is carried into the answer so a
|
|
4
|
+
* caller can say WHY, which is the difference between guidance a reviewer can
|
|
5
|
+
* argue with and a number they have to take on faith.
|
|
6
|
+
*/
|
|
7
|
+
export interface RelationshipConvention {
|
|
8
|
+
readonly kind: RelationshipKind;
|
|
9
|
+
readonly rule: string;
|
|
10
|
+
readonly because: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The kind practice expects from `from` to `to`, or `null` where practice has
|
|
14
|
+
* no expectation.
|
|
15
|
+
*
|
|
16
|
+
* Never contradicts the table: an answer is returned only when the table also
|
|
17
|
+
* permits it, so guidance can never point at an edge `check` would refuse.
|
|
18
|
+
*/
|
|
19
|
+
export declare const conventionalRelationshipKind: (from: string, to: string) => RelationshipConvention | null;
|
|
20
|
+
/** Every rule's name and sentence, for documentation and for the test that checks them. */
|
|
21
|
+
export declare const CONVENTION_RULES: readonly {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly kind: RelationshipKind;
|
|
24
|
+
readonly because: string;
|
|
25
|
+
}[];
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { conceptKinds, } from './profile.js';
|
|
2
|
+
import { isCoreConceptKindId, permittedRelationshipKinds, } from './relationship-matrix.js';
|
|
3
|
+
/**
|
|
4
|
+
* The relationship kind ArchiMate practice expects for a pair of core kinds,
|
|
5
|
+
* where practice has an expectation at all (#571).
|
|
6
|
+
*
|
|
7
|
+
* The relationship table is a PERMISSION table. It answers "may I draw this",
|
|
8
|
+
* and for an application component pointing at an application function it
|
|
9
|
+
* answers yes six times. Practice is much narrower: across this repository's
|
|
10
|
+
* own model and the ApertureX reference, 970 relationships over 72 core-kind
|
|
11
|
+
* pairs, the author used exactly one kind for 34 of the pairs the table lets
|
|
12
|
+
* them choose among, and used `assignment` for that particular pair 87 times
|
|
13
|
+
* out of 88. The engine knew the permission and never said the convention, so
|
|
14
|
+
* every palette offered six, every agent guessed, and a reviewer could never
|
|
15
|
+
* be told "this pair is usually X and yours is Y".
|
|
16
|
+
*
|
|
17
|
+
* ## Where these answers come from
|
|
18
|
+
*
|
|
19
|
+
* They are read off ArchiMate's own definitions of the relationships, NOT
|
|
20
|
+
* fitted to the two records. That direction matters. Both records agree, for
|
|
21
|
+
* instance, that a business actor points at a constraint with `association`,
|
|
22
|
+
* 87 times out of 87 - but that is one adopter's house convention for
|
|
23
|
+
* recording sign-off, not a rule of the language, and blessing it here would
|
|
24
|
+
* have told every other adopter their own style was wrong. The records are
|
|
25
|
+
* used as a CHECK: `test/relationship-convention.test.ts` asserts that every
|
|
26
|
+
* convention declared here matches real practice on a record that has enough
|
|
27
|
+
* of that pair to have a habit, and prints the disagreements when it does not.
|
|
28
|
+
*
|
|
29
|
+
* ## Silence is an answer
|
|
30
|
+
*
|
|
31
|
+
* `conventionalRelationshipKind` returns `null` far more often than not, and
|
|
32
|
+
* that is the point. A pair with no entry here is a pair where the language
|
|
33
|
+
* genuinely permits several readings and the author must choose: an artifact
|
|
34
|
+
* pointing at an application function is `realization` 41 times and
|
|
35
|
+
* `association` 16, a capability pointing at a goal splits evenly, and two
|
|
36
|
+
* application functions relate by composition, triggering, flow or serving
|
|
37
|
+
* depending on what the author means. Declaring a convention there would
|
|
38
|
+
* manufacture confidence the language does not have.
|
|
39
|
+
*
|
|
40
|
+
* Nothing here is a rule the compiler enforces. A permitted kind stays
|
|
41
|
+
* permitted; `check` never reads this file. It is guidance, offered to a
|
|
42
|
+
* palette, an agent, or a reviewer, and it says nothing when it has nothing
|
|
43
|
+
* to say.
|
|
44
|
+
*/
|
|
45
|
+
const KIND_META = new Map(conceptKinds.map((kind) => [kind.id, { layer: kind.layer, aspect: kind.aspect }]));
|
|
46
|
+
/** The three service elements: external behaviour a layer offers outward. */
|
|
47
|
+
const SERVICES = new Set([
|
|
48
|
+
'businessService',
|
|
49
|
+
'applicationService',
|
|
50
|
+
'technologyService',
|
|
51
|
+
]);
|
|
52
|
+
/** The three interface elements: the point at which a service is offered. */
|
|
53
|
+
const INTERFACES = new Set([
|
|
54
|
+
'businessInterface',
|
|
55
|
+
'applicationInterface',
|
|
56
|
+
'technologyInterface',
|
|
57
|
+
]);
|
|
58
|
+
const isService = (kind) => SERVICES.has(kind);
|
|
59
|
+
const isInterface = (kind) => INTERFACES.has(kind);
|
|
60
|
+
const isInternalBehaviour = (kind) => KIND_META.get(kind)?.aspect === 'behavior' && !isService(kind);
|
|
61
|
+
/**
|
|
62
|
+
* Ordered; the first rule that matches answers, and order is load-bearing
|
|
63
|
+
* twice. A service touching passive structure is `access` before it is
|
|
64
|
+
* anything else, because a service is behaviour first and an offering second.
|
|
65
|
+
* And an interface pointing at a service of its own layer is `assignment`,
|
|
66
|
+
* the interface exposing that service, while an interface pointing at
|
|
67
|
+
* anything else is `serving`. Both times the specific rule comes first.
|
|
68
|
+
*/
|
|
69
|
+
const RULES = [
|
|
70
|
+
{
|
|
71
|
+
name: 'behaviour-uses-data',
|
|
72
|
+
because: 'behaviour reads, writes or creates the passive structure it touches',
|
|
73
|
+
kind: 'access',
|
|
74
|
+
when: (from, to, f, t) => t.aspect === 'passive-structure' &&
|
|
75
|
+
(isInternalBehaviour(from) || isService(from)) &&
|
|
76
|
+
// An artifact is passive structure in the technology layer, and
|
|
77
|
+
// behaviour pointing at one is not "reading a file": both records write
|
|
78
|
+
// `association` there far more often than `access`. Left to the author.
|
|
79
|
+
to !== 'artifact',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'interface-exposes-service',
|
|
83
|
+
because: 'an interface is assigned to the service it exposes, not to the behaviour that uses it',
|
|
84
|
+
kind: 'assignment',
|
|
85
|
+
when: (from, to, f, t) => isInterface(from) && isService(to) && f.layer === t.layer,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'service-is-offered-outward',
|
|
89
|
+
because: 'a service is made available to whoever uses it',
|
|
90
|
+
kind: 'serving',
|
|
91
|
+
when: (from, _to, _f, t) => (isService(from) || isInterface(from)) &&
|
|
92
|
+
(t.aspect === 'active-structure' || t.aspect === 'behavior'),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'grouping-collects-members',
|
|
96
|
+
because: 'a grouping aggregates what it collects; the members exist without it',
|
|
97
|
+
kind: 'aggregation',
|
|
98
|
+
when: (from) => from === 'grouping',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'artifact-realizes-what-it-implements',
|
|
102
|
+
because: 'an artifact is the concrete file or deployable that fulfils the thing it stands for',
|
|
103
|
+
kind: 'realization',
|
|
104
|
+
when: (from, to, _f, t) => from === 'artifact' &&
|
|
105
|
+
(t.aspect === 'active-structure' || t.aspect === 'passive-structure') &&
|
|
106
|
+
to !== 'artifact',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'active-structure-performs-behaviour',
|
|
110
|
+
because: 'an active structure element is assigned to the internal behaviour it performs',
|
|
111
|
+
kind: 'assignment',
|
|
112
|
+
when: (from, to, f, t) => f.aspect === 'active-structure' &&
|
|
113
|
+
isInternalBehaviour(to) &&
|
|
114
|
+
f.layer === t.layer,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'internal-realizes-service',
|
|
118
|
+
because: 'the internal element is the concrete thing that fulfils the service its layer offers',
|
|
119
|
+
kind: 'realization',
|
|
120
|
+
when: (from, to, f, t) => isService(to) &&
|
|
121
|
+
f.layer === t.layer &&
|
|
122
|
+
(f.aspect === 'active-structure' || isInternalBehaviour(from)) &&
|
|
123
|
+
!isInterface(from),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'technology-serves-application',
|
|
127
|
+
// The least certain rule here. The authoring reference lists this pair as
|
|
128
|
+
// "realizes or serves", two readings: realization when the node IS the
|
|
129
|
+
// implementation, serving when it hosts one. Both records are unanimous
|
|
130
|
+
// for `serving`, ten times in ten, so that is what is offered - and the
|
|
131
|
+
// other reading stays one click away, as every non-conventional kind does.
|
|
132
|
+
because: 'a technology element is not part of the application it runs; it serves it',
|
|
133
|
+
kind: 'serving',
|
|
134
|
+
when: (_from, _to, f, t) => f.layer === 'technology' &&
|
|
135
|
+
t.layer === 'application' &&
|
|
136
|
+
(t.aspect === 'active-structure' || t.aspect === 'behavior'),
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
const cache = new Map();
|
|
140
|
+
/**
|
|
141
|
+
* The kind practice expects from `from` to `to`, or `null` where practice has
|
|
142
|
+
* no expectation.
|
|
143
|
+
*
|
|
144
|
+
* Never contradicts the table: an answer is returned only when the table also
|
|
145
|
+
* permits it, so guidance can never point at an edge `check` would refuse.
|
|
146
|
+
*/
|
|
147
|
+
export const conventionalRelationshipKind = (from, to) => {
|
|
148
|
+
const key = `${from}>${to}`;
|
|
149
|
+
const cached = cache.get(key);
|
|
150
|
+
if (cached !== undefined)
|
|
151
|
+
return cached;
|
|
152
|
+
const answer = compute(from, to);
|
|
153
|
+
cache.set(key, answer);
|
|
154
|
+
return answer;
|
|
155
|
+
};
|
|
156
|
+
const compute = (from, to) => {
|
|
157
|
+
if (!isCoreConceptKindId(from) || !isCoreConceptKindId(to))
|
|
158
|
+
return null;
|
|
159
|
+
const fromMeta = KIND_META.get(from);
|
|
160
|
+
const toMeta = KIND_META.get(to);
|
|
161
|
+
if (fromMeta === undefined || toMeta === undefined)
|
|
162
|
+
return null;
|
|
163
|
+
for (const rule of RULES) {
|
|
164
|
+
if (!rule.when(from, to, fromMeta, toMeta))
|
|
165
|
+
continue;
|
|
166
|
+
// Guidance that the table forbids would be worse than none: it would send
|
|
167
|
+
// an author at an edge `check` refuses. A rule that lands outside the
|
|
168
|
+
// permission is treated as having no answer for this pair.
|
|
169
|
+
if (!permittedRelationshipKinds(from, to).has(rule.kind))
|
|
170
|
+
return null;
|
|
171
|
+
return { kind: rule.kind, rule: rule.name, because: rule.because };
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
/** Every rule's name and sentence, for documentation and for the test that checks them. */
|
|
176
|
+
export const CONVENTION_RULES = RULES.map(({ name, kind, because }) => ({ name, kind, because }));
|