yarramate 1.34.0 → 1.35.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.
@@ -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 live. What work, deliverable, rule or decision is in
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 live risk with no mitigation is a bet the engagement is making without
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
- format: "yarramate/projection/v1",
262
- id: "ad-hoc",
263
- version: "0",
264
- query,
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 ? {} : { presentation: { nesting } }),
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
@@ -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) => row.type === 'risk' && row.status === 'current' && row.mitigatedBy.length === 0);
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 (current risks): ${list(log.gaps.unmitigated)}`, `- Unconfirmed assumptions: ${list(log.gaps.unconfirmed)}`, `- Unreviewed risks: ${list(log.gaps.unreviewed)}`, '');
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
  }
@@ -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;
@@ -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
- if ((relationshipMode === 'between' &&
567
- sourceSelected &&
568
- targetSelected) ||
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') {