process-graph 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -83,7 +83,7 @@ if (missing.length > 0) {
83
83
  process.stderr.write(missingPeersMessage(missing, declaredPeers(), process.env.npm_config_user_agent));
84
84
  process.exitCode = exitCodes.missingPeers;
85
85
  } else {
86
- const { packageVersion, runProcess } = await import("./dist-IjrgWhHQ.js");
86
+ const { packageVersion, runProcess } = await import("./dist-Cw63tKU9.js");
87
87
  process.exitCode = await runProcess({
88
88
  name: "process-graph",
89
89
  version: packageVersion(manifest)
package/dist/cli.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { F as Status, _ as StatusRule, k as Facts, m as IncomingBasis, p as ArtefactBasis } from "./index-CCfe0csl.js";
1
+ import { A as Facts, B as Status, _ as StatusRule, k as FactRecord, m as IncomingBasis, p as ArtefactBasis } from "./index-BH1aFqkJ.js";
2
2
  import { E as Participant, F as OverlayStatus, G as IdSpace, K as OpenIds, S as EventDef, a as Overlay, b as ArtefactDef, g as Model, k as Transition, x as Element } from "./index-CmZOswkx.js";
3
3
  import { C as Gap, n as GraphqlFetch, t as GitHubTokenPort, v as GraphSourceClient } from "./graphql-C1j5wQFz.js";
4
- import { Q as GraphStoreClient } from "./index-DhbvDgK4.js";
4
+ import { ut as GraphStoreClient } from "./index-DxWOY8f9.js";
5
5
  import "./index-CptUHq6F.js";
6
6
  import { A as Flowchart, E as Swimlane, M as Handoff, g as SequenceDiagram, x as StateMachine } from "./index-CoA6vXqe.js";
7
+ import { ComputedValue } from "trilean";
7
8
  //#region ../graph-cli/dist/exit-codes.d.ts
8
9
  /**
9
10
  * The exit status of every command, by meaning. A script tells the outcomes apart without reading the output: a model with problems is not a failure to run, and neither is an element that does not exist.
@@ -61,7 +62,7 @@ interface CliPorts {
61
62
  * @param signal - Aborts the read.
62
63
  */
63
64
  readonly readFile: (path: string, signal: AbortSignal | undefined) => Promise<string>;
64
- /** The facts that decision conditions read, and which input artefacts are provided. */
65
+ /** Facts the host supplies for decision conditions and input artefacts. The model's stored fact record (what `set artefact` and `set fact` recorded) answers first; these answer whatever it does not. */
65
66
  readonly facts: Facts;
66
67
  /** Aborts every store and source call a command makes. */
67
68
  readonly signal: AbortSignal | undefined;
@@ -123,7 +124,7 @@ interface NodePortsOptions {
123
124
  readonly fetch?: GraphqlFetch;
124
125
  /** The clock, in milliseconds since the epoch, for a rate limit's retry time; `Date.now` when absent. */
125
126
  readonly now?: () => number;
126
- /** The facts decision conditions read and which input artefacts are provided; nothing is known when absent. */
127
+ /** Facts the host supplies for decision conditions and input artefacts, answering whatever the model's stored fact record does not; nothing more is known when absent. */
127
128
  readonly facts?: Facts;
128
129
  /** Aborts every store and source call. */
129
130
  readonly signal?: AbortSignal;
@@ -162,20 +163,29 @@ export declare const nodePorts: (options?: NodePortsOptions) => CliPorts;
162
163
  export declare const runProcess: (identity: ProgramIdentity) => Promise<ExitCode>;
163
164
  //#endregion
164
165
  //#region ../graph-cli/dist/session.d.ts
165
- /** What every operation works against: one model in one store, the facts its conditions read, and the signal that aborts its calls. */
166
+ /** What every operation works against: one model in one store, the facts a host supplies for its conditions, and the signal that aborts its calls. */
166
167
  interface GraphSession {
167
168
  readonly store: GraphStoreClient;
168
169
  /** The name the model is filed under in the store. */
169
170
  readonly modelId: string;
171
+ /** Facts the host supplies, consulted for whatever the model's stored fact record does not answer (see `runFacts`). */
170
172
  readonly facts: Facts;
171
173
  readonly signal: AbortSignal | undefined;
172
174
  }
173
- /** A stored model and its run state, as an operation reads them. */
175
+ /** A stored model, its run state and its fact record, as an operation reads them. */
174
176
  interface StoredRun {
175
177
  readonly revision: number;
176
178
  readonly model: Model<OpenIds>;
177
179
  readonly overlay: Overlay<OpenIds>;
180
+ readonly facts: FactRecord;
178
181
  }
182
+ /**
183
+ * The facts a run's reasoning reads: the model's stored fact record first, then the session's own facts for every key the record does not answer. What `set artefact` and `set fact` record therefore overrides a host's facts only where it says something.
184
+ * @param session - The session, whose facts the host supplied.
185
+ * @param run - The stored run.
186
+ * @returns The `Facts` port for graph-domain.
187
+ */
188
+ export declare const runFacts: (session: GraphSession, run: StoredRun) => Facts;
179
189
  /** An element as commands report it: its identifier and its human label. */
180
190
  interface ElementSummary {
181
191
  readonly id: string;
@@ -394,6 +404,50 @@ interface SetResult extends Written {
394
404
  * @throws CliError with `notFound` when the model or the element does not exist.
395
405
  */
396
406
  export declare const setState: (session: GraphSession, id: string, state: OverlayStatus) => Promise<SetResult>;
407
+ /** What can be recorded about an artefact: `provided`, known not provided (`absent`), or `unknown`, which forgets whatever was recorded. */
408
+ export declare const artefactStates: readonly ["provided", "absent", "unknown"];
409
+ /** One of `artefactStates`. */
410
+ type ArtefactState = (typeof artefactStates)[number];
411
+ /** The state an artefact was given. */
412
+ interface SetArtefactResult extends Written {
413
+ readonly artefact: string;
414
+ readonly state: ArtefactState;
415
+ }
416
+ /**
417
+ * Records whether an artefact of the session's model is provided, so a consumer of an input no activity produces can become ready, or forgets it with `unknown`.
418
+ * @param session - The store and model.
419
+ * @param id - The artefact.
420
+ * @param state - `provided`, `absent` or `unknown`.
421
+ * @returns The artefact, its new state and the revision the change took.
422
+ * @throws CliError with `notFound` when the model or the artefact does not exist.
423
+ */
424
+ export declare const setArtefact: (session: GraphSession, id: string, state: ArtefactState) => Promise<SetArtefactResult>;
425
+ /** The value a named fact was given, or `unknown` when it was forgotten. */
426
+ interface SetFactResult extends Written {
427
+ readonly fact: string;
428
+ readonly value: ComputedValue | "unknown";
429
+ }
430
+ /**
431
+ * Records a named fact's value for the session's model, so every condition reading that name evaluates against it, or forgets it with `unknown`, so those conditions are indeterminate again.
432
+ * @param session - The store and model.
433
+ * @param name - The fact's name, the key a condition's `reference` node gives.
434
+ * @param value - Its trilean value, or `unknown`.
435
+ * @returns The fact, its new value and the revision the change took.
436
+ * @throws CliError with `notFound` when the model does not exist.
437
+ */
438
+ export declare const setFact: (session: GraphSession, name: string, value: ComputedValue | "unknown") => Promise<SetFactResult>;
439
+ /** The session's model's fact record. */
440
+ interface FactsResult {
441
+ readonly revision: number;
442
+ readonly facts: FactRecord;
443
+ }
444
+ /**
445
+ * Reads what is recorded about the session's model's run: which artefacts are provided or absent, and each named fact's value.
446
+ * @param session - The store and model.
447
+ * @returns The fact record and the model's revision.
448
+ * @throws CliError with `notFound` when the model does not exist.
449
+ */
450
+ export declare const factsOf: (session: GraphSession) => Promise<FactsResult>;
397
451
  //#endregion
398
452
  //#region ../graph-cli/dist/validate.d.ts
399
453
  /** One problem with a model. */
@@ -509,4 +563,4 @@ export declare const stateMachineToDot: <I extends IdSpace>(view: StateMachine<I
509
563
  */
510
564
  export declare const sequenceToDot: <I extends IdSpace>(view: SequenceDiagram<I>, participantLabel: (participant: I["actor"]) => string) => string;
511
565
  //#endregion
512
- export type { Addition, BlockedResult, CliPorts, CyclesResult, ElementStatus, ElementSummary, ExitCode, Failure, FormatName, GraphSession, HandoffsResult, ImpactResult, NodePortsOptions, OpenedStore, PathResult, ProgramIdentity, ProjectedView, ReadyResult, SetResult, ShowRequest, ShowResult, SourceLookup, StoredRun, SyncRequest, SyncResult, ValidateResult, ValidationIssue, ViewName, WhyLink, WhyResult, Written };
566
+ export type { Addition, ArtefactState, BlockedResult, CliPorts, CyclesResult, ElementStatus, ElementSummary, ExitCode, FactsResult, Failure, FormatName, GraphSession, HandoffsResult, ImpactResult, NodePortsOptions, OpenedStore, PathResult, ProgramIdentity, ProjectedView, ReadyResult, SetArtefactResult, SetFactResult, SetResult, ShowRequest, ShowResult, SourceLookup, StoredRun, SyncRequest, SyncResult, ValidateResult, ValidationIssue, ViewName, WhyLink, WhyResult, Written };
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as exitCodes } from "./exit-codes-0ultCqdS.js";
2
- import { A as add, C as blocked, D as path, E as impactOf, F as failureOf, M as findRun, N as loadRun, O as ready, P as CliError, S as swimlaneToMermaid, T as handoffsOf, _ as stateMachineToDot, a as defaultModelId, b as sequenceToMermaid, c as validateStored, d as formatNames, f as project, g as sequenceToDot, h as flowchartToDot, i as runProcess, j as setState, k as why, l as sync, m as viewNames, n as nodePorts, o as runCli, p as show, r as packageVersion, s as validateDocument, t as defaultDataPath, u as emit, v as swimlaneToDot, w as cycles, x as stateMachineToMermaid, y as flowchartToMermaid } from "./dist-HLlW8vgT.js";
3
- export { CliError, add, blocked, cycles, defaultDataPath, defaultModelId, emit, exitCodes, failureOf, findRun, flowchartToDot, flowchartToMermaid, formatNames, handoffsOf, impactOf, loadRun, nodePorts, packageVersion, path, project, ready, runCli, runProcess, sequenceToDot, sequenceToMermaid, setState, show, stateMachineToDot, stateMachineToMermaid, swimlaneToDot, swimlaneToMermaid, sync, validateDocument, validateStored, viewNames, why };
2
+ import { A as add, B as failureOf, C as blocked, D as path, E as impactOf, F as setState, I as findRun, L as loadRun, M as factsOf, N as setArtefact, O as ready, P as setFact, R as runFacts, S as swimlaneToMermaid, T as handoffsOf, _ as stateMachineToDot, a as defaultModelId, b as sequenceToMermaid, c as validateStored, d as formatNames, f as project, g as sequenceToDot, h as flowchartToDot, i as runProcess, j as artefactStates, k as why, l as sync, m as viewNames, n as nodePorts, o as runCli, p as show, r as packageVersion, s as validateDocument, t as defaultDataPath, u as emit, v as swimlaneToDot, w as cycles, x as stateMachineToMermaid, y as flowchartToMermaid, z as CliError } from "./dist-BxHnxpcT.js";
3
+ export { CliError, add, artefactStates, blocked, cycles, defaultDataPath, defaultModelId, emit, exitCodes, factsOf, failureOf, findRun, flowchartToDot, flowchartToMermaid, formatNames, handoffsOf, impactOf, loadRun, nodePorts, packageVersion, path, project, ready, runCli, runFacts, runProcess, sequenceToDot, sequenceToMermaid, setArtefact, setFact, setState, show, stateMachineToDot, stateMachineToMermaid, swimlaneToDot, swimlaneToMermaid, sync, validateDocument, validateStored, viewNames, why };
@@ -1,3 +1,4 @@
1
+ import { createSyncEvaluator } from "trilean";
1
2
  //#region ../../core/graph/dag/dist/errors.js
2
3
  /**
3
4
  * Thrown when an operation that is only defined on an acyclic graph is given one that contains a cycle.
@@ -233,6 +234,167 @@ function assertAcyclic(dag) {
233
234
  if (cycles.length > 0) throw new CycleError(cycles);
234
235
  }
235
236
  //#endregion
237
+ //#region ../../core/graph/domain/dist/facts.js
238
+ /** The only field of a reserved key. Keys that carry it are resolved from the overlay, so a `Facts` implementation must not use it. */
239
+ const elementField = "$element";
240
+ /**
241
+ * Builds the reserved key that reads another element's run state inside a condition. It resolves to the text `"done"` or `"open"`, and is not found (so the condition is indeterminate) when the overlay says `"unknown"` or the id is not an element of the model.
242
+ *
243
+ * The value is the overlay's own state, not the derived status: a condition therefore never depends on the derivation that evaluates it, and cannot form a cycle.
244
+ * @param id - The element whose state is read.
245
+ * @returns A key to put in a trilean `reference` node.
246
+ */
247
+ const elementStatusKey = (id) => ({ [elementField]: id });
248
+ /** The only field of the key the package puts to the port to ask whether an input artefact is provided. */
249
+ const artefactField = "$artefact";
250
+ /**
251
+ * Builds the key the package asks the facts port when it needs to know whether an artefact has been provided from outside the process. An artefact is provided exactly when the port answers `{ found: true }` with the boolean `true`; a not-found or `false` answer means it is not.
252
+ *
253
+ * A provided artefact satisfies every consumer, whether or not an activity also produces it.
254
+ * @param id - The artefact.
255
+ * @returns The key the port is asked.
256
+ */
257
+ const artefactKey = (id) => ({ [artefactField]: id });
258
+ /**
259
+ * Whether the facts port reports an artefact as provided.
260
+ * @param facts - The injected source of facts.
261
+ * @param id - The artefact.
262
+ */
263
+ const isProvided = (facts, id) => {
264
+ const answer = facts(artefactKey(id));
265
+ return answer.found && answer.value.kind === "boolean" && answer.value.value;
266
+ };
267
+ /** A record that knows nothing: no artefact is provided and every named fact is unknown. */
268
+ const emptyFactRecord = {
269
+ artefacts: {},
270
+ values: {}
271
+ };
272
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
273
+ /** The artefact id an `artefactKey` key names, or `undefined` for any other key. */
274
+ const artefactOf = (key) => {
275
+ if (!isRecord(key) || Object.keys(key).length !== 1) return;
276
+ const id = key[artefactField];
277
+ return typeof id === "string" ? id : void 0;
278
+ };
279
+ /**
280
+ * Adapts a fact record to the `Facts` port. A string key is a named fact, answered with its value when the record holds it; a key built by `artefactKey` is answered with the boolean the record holds for that artefact; every other key, and any name or artefact the record does not hold, is not found.
281
+ * @param record - What is known.
282
+ * @returns A `Facts` port reading the record.
283
+ */
284
+ const factsFrom = (record) => {
285
+ const values = new Map(Object.entries(record.values));
286
+ const artefacts = new Map(Object.entries(record.artefacts));
287
+ return (key) => {
288
+ if (typeof key === "string") {
289
+ const value = values.get(key);
290
+ return value === void 0 ? { found: false } : {
291
+ found: true,
292
+ value
293
+ };
294
+ }
295
+ const id = artefactOf(key);
296
+ const provided = id === void 0 ? void 0 : artefacts.get(id);
297
+ return provided === void 0 ? { found: false } : {
298
+ found: true,
299
+ value: {
300
+ kind: "boolean",
301
+ value: provided
302
+ }
303
+ };
304
+ };
305
+ };
306
+ /**
307
+ * The named facts a condition reads: the string key of every `reference` node in it, each once, in the order first met walking the tree depth first. Reserved keys and other non-string keys are not names, so they are left out, and a reference's own key is never searched for further references.
308
+ * @param condition - A trilean predicate or expression, such as a transition's `condition`.
309
+ * @returns The names, each once.
310
+ */
311
+ const factNamesIn = (condition) => {
312
+ const names = /* @__PURE__ */ new Set();
313
+ const pending = [condition];
314
+ for (let node = pending.pop(); node !== void 0; node = pending.pop()) if (Array.isArray(node)) {
315
+ const items = node;
316
+ pending.push(...[...items].reverse());
317
+ } else if (typeof node === "object" && node !== null) {
318
+ if ("kind" in node && node.kind === "reference" && "key" in node) {
319
+ if (typeof node.key === "string") names.add(node.key);
320
+ } else {
321
+ const fields = Object.values(node);
322
+ pending.push(...[...fields].reverse());
323
+ }
324
+ }
325
+ return [...names];
326
+ };
327
+ /**
328
+ * Layers facts ports: each key is answered by the first port that finds it, so an earlier port overrides a later one only where it knows the answer.
329
+ * @param ports - The ports, most authoritative first.
330
+ * @returns A `Facts` port that is not found only when no port finds the key.
331
+ */
332
+ const layeredFacts = (...ports) => (key) => {
333
+ for (const port of ports) {
334
+ const answer = port(key);
335
+ if (answer.found) return answer;
336
+ }
337
+ return { found: false };
338
+ };
339
+ /**
340
+ * Thrown when a condition uses a node whose evaluation needs a table or a collection from the facts port, which answers single keys only.
341
+ */
342
+ var UnsupportedConditionError = class extends Error {
343
+ /** The kind of reference the condition used. */
344
+ reference;
345
+ /**
346
+ * @param reference - The unsupported kind of reference.
347
+ */
348
+ constructor(reference) {
349
+ super(`A condition uses a ${reference} reference, which the Facts port does not answer: it resolves single keys only`);
350
+ this.name = "UnsupportedConditionError";
351
+ this.reference = reference;
352
+ }
353
+ };
354
+ const evaluator = createSyncEvaluator({});
355
+ /**
356
+ * Evaluates every transition's condition.
357
+ *
358
+ * A definite true is `taken`, a definite false is `not-taken` and an indeterminate result is `indeterminate`. A transition without a condition is `taken`. Conditions are evaluated for every transition regardless of the state of its source.
359
+ * @param model - The model whose transitions are evaluated.
360
+ * @param overlay - Run state, read by `elementStatusKey` references.
361
+ * @param facts - The injected source of facts.
362
+ * @returns The outcome of every transition, in transition order.
363
+ * @throws UnsupportedConditionError when a condition contains a `lookup`, `some`, `every` or fold node.
364
+ */
365
+ const evaluateBranches = (model, overlay, facts) => {
366
+ const elementIds = new Set(model.elements.map(({ id }) => id));
367
+ const overlayOf = new Map(Object.entries(overlay));
368
+ const resolvers = {
369
+ resolveValue: (key) => {
370
+ if (!isRecord(key) || !(elementField in key)) return facts(key);
371
+ const id = key[elementField];
372
+ if (typeof id !== "string" || !elementIds.has(id)) return { found: false };
373
+ const state = overlayOf.get(id);
374
+ if (state === "unknown") return { found: false };
375
+ return {
376
+ found: true,
377
+ value: {
378
+ kind: "text",
379
+ value: state === "done" ? "done" : "open"
380
+ }
381
+ };
382
+ },
383
+ resolveLookup: () => {
384
+ throw new UnsupportedConditionError("lookup");
385
+ },
386
+ resolveCollection: () => {
387
+ throw new UnsupportedConditionError("collection");
388
+ }
389
+ };
390
+ return new Map(model.transitions.map(({ id, condition }) => {
391
+ if (condition === void 0) return [id, "taken"];
392
+ const result = evaluator.evaluatePredicate(condition, void 0, resolvers);
393
+ if (result.status === "indeterminate") return [id, "indeterminate"];
394
+ return [id, result.value ? "taken" : "not-taken"];
395
+ }));
396
+ };
397
+ //#endregion
236
398
  //#region ../../core/graph/domain/dist/control.js
237
399
  /**
238
400
  * Builds the dependency graph over every element, leaving out the transitions in `excluded`. A node needs the source of each remaining incoming transition and, for an activity, the producer of each artefact it consumes.
@@ -291,4 +453,4 @@ const controlDag = (model) => buildControl(model, new Set(loopTransitions(model)
291
453
  */
292
454
  const illegalCycles = (model) => findCycles(controlDag(model));
293
455
  //#endregion
294
- export { findCycles as a, descendantsOf as c, lookup as d, CycleError as f, assertAcyclic as i, assembleDag as l, illegalCycles as n, stronglyConnectedComponents as o, UnknownNodeError as p, loopTransitions as r, ancestorsOf as s, controlDag as t, buildDag as u };
456
+ export { UnknownNodeError as S, descendantsOf as _, artefactKey as a, lookup as b, evaluateBranches as c, isProvided as d, layeredFacts as f, ancestorsOf as g, stronglyConnectedComponents as h, UnsupportedConditionError as i, factNamesIn as l, findCycles as m, illegalCycles as n, elementStatusKey as o, assertAcyclic as p, loopTransitions as r, emptyFactRecord as s, controlDag as t, factsFrom as u, assembleDag as v, CycleError as x, buildDag as y };
@@ -1,13 +1,13 @@
1
1
  import { a as validateOverlay, d as overlayStatuses, f as participantKinds } from "./schema-CVnS6HiX.js";
2
2
  import { t as validate } from "./validate-DF3mQZww.js";
3
3
  import { n as parseModel, t as ModelParseError } from "./parse-CuipC_2K.js";
4
- import { _ as toSwimlane, b as handoffs, c as deriveStatuses, g as toStateMachine, i as readySet, m as toSequence, n as criticalPath, r as impact, s as explainStatus, t as blockedBy, y as toFlowchart } from "./queries-CCexJAE2.js";
5
- import { f as CycleError, n as illegalCycles, p as UnknownNodeError } from "./control-aDk9560d.js";
4
+ import { c as deriveStatuses, f as toStateMachine, g as handoffs, h as toFlowchart, i as readySet, n as criticalPath, p as toSwimlane, r as impact, s as explainStatus, t as blockedBy, u as toSequence } from "./queries-C8kIDRiY.js";
5
+ import { S as UnknownNodeError, f as layeredFacts, n as illegalCycles, u as factsFrom, x as CycleError } from "./control-DiygoEhm.js";
6
6
  import { l as assembleSync, t as createGitHubIssuesSource, u as createGraphSourceRouter } from "./source-DRDpEA8Q.js";
7
7
  import { t as githubTokenFromEnvironment } from "./node-AchI1T-K.js";
8
- import { f as emptyModel } from "./store-BcR70CaO.js";
9
- import { t as createGraphStoreRouter } from "./router-D4gtTlXm.js";
10
- import { t as openNodeGraphStore } from "./open-CyRXJ4bt.js";
8
+ import { g as emptyModel } from "./store-BJILV4JS.js";
9
+ import { t as createGraphStoreRouter } from "./router-DvGGLnNt.js";
10
+ import { t as openNodeGraphStore } from "./open-BF-UHqeW.js";
11
11
  import { t as exitCodes } from "./exit-codes-0ultCqdS.js";
12
12
  import { ORPCError, createRouterClient } from "@orpc/server";
13
13
  import { Argument, Command, CommanderError, Option } from "commander";
@@ -95,6 +95,13 @@ const failureOf = (error) => {
95
95
  //#endregion
96
96
  //#region ../graph-cli/dist/session.js
97
97
  /**
98
+ * The facts a run's reasoning reads: the model's stored fact record first, then the session's own facts for every key the record does not answer. What `set artefact` and `set fact` record therefore overrides a host's facts only where it says something.
99
+ * @param session - The session, whose facts the host supplied.
100
+ * @param run - The stored run.
101
+ * @returns The `Facts` port for graph-domain.
102
+ */
103
+ const runFacts = (session, run) => layeredFacts(factsFrom(run.facts), session.facts);
104
+ /**
98
105
  * The oRPC client options carrying the session's signal. The key is left out when there is no signal, since the options type does not accept an explicit `undefined`.
99
106
  * @param signal - The signal, if any.
100
107
  */
@@ -110,7 +117,8 @@ const findRun = async (session) => {
110
117
  return {
111
118
  revision: stored.revision,
112
119
  model: stored.model,
113
- overlay: stored.overlay
120
+ overlay: stored.overlay,
121
+ facts: stored.facts
114
122
  };
115
123
  } catch (error) {
116
124
  if (error instanceof ORPCError && error.code === "NOT_FOUND") return;
@@ -212,6 +220,74 @@ const setState = async (session, id, state) => {
212
220
  revision
213
221
  };
214
222
  };
223
+ /** What can be recorded about an artefact: `provided`, known not provided (`absent`), or `unknown`, which forgets whatever was recorded. */
224
+ const artefactStates = [
225
+ "provided",
226
+ "absent",
227
+ "unknown"
228
+ ];
229
+ /**
230
+ * Records whether an artefact of the session's model is provided, so a consumer of an input no activity produces can become ready, or forgets it with `unknown`.
231
+ * @param session - The store and model.
232
+ * @param id - The artefact.
233
+ * @param state - `provided`, `absent` or `unknown`.
234
+ * @returns The artefact, its new state and the revision the change took.
235
+ * @throws CliError with `notFound` when the model or the artefact does not exist.
236
+ */
237
+ const setArtefact = async (session, id, state) => {
238
+ const { model } = await loadRun(session);
239
+ if (!model.artefacts.some((artefact) => artefact.id === id)) throw new CliError(exitCodes.notFound, `no artefact "${id}" in the model`);
240
+ const options = callOptions(session.signal);
241
+ const { revision } = state === "unknown" ? await session.store.v1.facts.clear({
242
+ modelId: session.modelId,
243
+ facts: { artefacts: [id] }
244
+ }, options) : await session.store.v1.facts.set({
245
+ modelId: session.modelId,
246
+ facts: { artefacts: { [id]: state === "provided" } }
247
+ }, options);
248
+ return {
249
+ artefact: id,
250
+ state,
251
+ revision
252
+ };
253
+ };
254
+ /**
255
+ * Records a named fact's value for the session's model, so every condition reading that name evaluates against it, or forgets it with `unknown`, so those conditions are indeterminate again.
256
+ * @param session - The store and model.
257
+ * @param name - The fact's name, the key a condition's `reference` node gives.
258
+ * @param value - Its trilean value, or `unknown`.
259
+ * @returns The fact, its new value and the revision the change took.
260
+ * @throws CliError with `notFound` when the model does not exist.
261
+ */
262
+ const setFact = async (session, name, value) => {
263
+ await loadRun(session);
264
+ const options = callOptions(session.signal);
265
+ const { revision } = value === "unknown" ? await session.store.v1.facts.clear({
266
+ modelId: session.modelId,
267
+ facts: { values: [name] }
268
+ }, options) : await session.store.v1.facts.set({
269
+ modelId: session.modelId,
270
+ facts: { values: { [name]: value } }
271
+ }, options);
272
+ return {
273
+ fact: name,
274
+ value,
275
+ revision
276
+ };
277
+ };
278
+ /**
279
+ * Reads what is recorded about the session's model's run: which artefacts are provided or absent, and each named fact's value.
280
+ * @param session - The store and model.
281
+ * @returns The fact record and the model's revision.
282
+ * @throws CliError with `notFound` when the model does not exist.
283
+ */
284
+ const factsOf = async (session) => {
285
+ const { revision, facts } = await loadRun(session);
286
+ return {
287
+ revision,
288
+ facts
289
+ };
290
+ };
215
291
  //#endregion
216
292
  //#region ../graph-cli/dist/queries.js
217
293
  /** Each element counts as one step, so the critical path is the longest chain of elements. */
@@ -221,8 +297,9 @@ const stepWeight = 1;
221
297
  * @param session - The store, model and facts.
222
298
  */
223
299
  const ready = async (session) => {
224
- const { model, overlay } = await loadRun(session);
225
- return { elements: summarise(model, readySet(model, overlay, session.facts)) };
300
+ const run = await loadRun(session);
301
+ const { model, overlay } = run;
302
+ return { elements: summarise(model, readySet(model, overlay, runFacts(session, run))) };
226
303
  };
227
304
  const statusOf = (statuses, summary) => {
228
305
  const status = statuses.get(summary.id);
@@ -238,15 +315,17 @@ const statusOf = (statuses, summary) => {
238
315
  * @param id - The element asked about.
239
316
  */
240
317
  const blocked = async (session, id) => {
241
- const { model, overlay } = await loadRun(session);
318
+ const run = await loadRun(session);
319
+ const { model, overlay } = run;
320
+ const facts = runFacts(session, run);
242
321
  const element = requireElement(model, id);
243
- const statuses = deriveStatuses(model, overlay, session.facts);
322
+ const statuses = deriveStatuses(model, overlay, facts);
244
323
  return {
245
324
  element: statusOf(statuses, {
246
325
  id,
247
326
  label: labelOf(element)
248
327
  }),
249
- blockedBy: summarise(model, blockedBy(model, overlay, session.facts, id))
328
+ blockedBy: summarise(model, blockedBy(model, overlay, facts, id))
250
329
  };
251
330
  };
252
331
  /**
@@ -255,15 +334,17 @@ const blocked = async (session, id) => {
255
334
  * @param id - The element asked about.
256
335
  */
257
336
  const impactOf = async (session, id) => {
258
- const { model, overlay } = await loadRun(session);
337
+ const run = await loadRun(session);
338
+ const { model, overlay } = run;
339
+ const facts = runFacts(session, run);
259
340
  const element = requireElement(model, id);
260
- const statuses = deriveStatuses(model, overlay, session.facts);
341
+ const statuses = deriveStatuses(model, overlay, facts);
261
342
  return {
262
343
  element: statusOf(statuses, {
263
344
  id,
264
345
  label: labelOf(element)
265
346
  }),
266
- impacted: summarise(model, impact(model, overlay, session.facts, id))
347
+ impacted: summarise(model, impact(model, overlay, facts, id))
267
348
  };
268
349
  };
269
350
  /**
@@ -271,8 +352,9 @@ const impactOf = async (session, id) => {
271
352
  * @param session - The store, model and facts.
272
353
  */
273
354
  const path = async (session) => {
274
- const { model, overlay } = await loadRun(session);
275
- const found = criticalPath(model, overlay, session.facts, () => stepWeight);
355
+ const run = await loadRun(session);
356
+ const { model, overlay } = run;
357
+ const found = criticalPath(model, overlay, runFacts(session, run), () => stepWeight);
276
358
  return {
277
359
  path: summarise(model, found.path),
278
360
  weight: found.weight
@@ -300,9 +382,10 @@ const handoffsOf = async (session) => {
300
382
  * @param id - The element asked about.
301
383
  */
302
384
  const why = async (session, id) => {
303
- const { model, overlay } = await loadRun(session);
385
+ const run = await loadRun(session);
386
+ const { model, overlay } = run;
304
387
  requireElement(model, id);
305
- const explained = explainStatus(model, overlay, session.facts, id);
388
+ const explained = explainStatus(model, overlay, runFacts(session, run), id);
306
389
  const chain = explained.chain.map((link) => ({
307
390
  id: link.element,
308
391
  label: labelOf(requireElement(model, link.element)),
@@ -375,6 +458,17 @@ const renderWhy = (result) => {
375
458
  const renderAdded = (kind, id, written) => lines([`added ${kind} ${id} at revision ${String(written.revision)}`]);
376
459
  /** `set`: the element's new run state and the revision it took. */
377
460
  const renderSet = (result) => lines([`set ${result.element} to ${result.state} at revision ${String(result.revision)}`]);
461
+ /** A fact's value as the command line reads it back: a boolean or number as written, text as it is, and the other kinds as their JSON. */
462
+ const valueText = (value) => {
463
+ if (value.kind === "boolean" || value.kind === "number") return value.kind === "number" && value.unit !== void 0 ? JSON.stringify(value) : String(value.value);
464
+ return value.kind === "text" ? value.value : JSON.stringify(value);
465
+ };
466
+ /** `set artefact`: the artefact's new state and the revision it took. */
467
+ const renderSetArtefact = (result) => lines([`set artefact ${result.artefact} to ${result.state} at revision ${String(result.revision)}`]);
468
+ /** `set fact`: the fact's new value and the revision it took. */
469
+ const renderSetFact = (result) => lines([`set fact ${result.fact} to ${result.value === "unknown" ? "unknown" : valueText(result.value)} at revision ${String(result.revision)}`]);
470
+ /** `facts`: each recorded artefact as `artefact`, its id and `provided` or `absent`, then each named fact as `fact`, its name and its value, each sorted by id or name. */
471
+ const renderFacts = (result) => lines([...Object.entries(result.facts.artefacts).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([id, provided]) => `artefact\t${id}\t${provided ? "provided" : "absent"}`), ...Object.entries(result.facts.values).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => `fact\t${name}\t${valueText(value)}`)]);
378
472
  /** `sync`: what was written, then each gap as its reason, target and message. */
379
473
  const renderSync = (result) => lines([`synced ${String(result.activities)} activities, ${String(result.transitions)} transitions and ${String(result.participants)} participants into "${result.modelId}" at revision ${String(result.revision)}`, ...result.gaps.map((gap) => `gap\t${gap.reason}\t${gap.target}\t${gap.message}`)]);
380
474
  /** `validate`: `valid`, or each issue as its code, its JSON pointer when it has one, and its message. */
@@ -962,12 +1056,42 @@ const readGlobals = (command, ports) => {
962
1056
  json: json === true
963
1057
  };
964
1058
  };
1059
+ /** Narrows a variadic option's values, which commander gives as an untyped array, to the strings they are. */
1060
+ const isText = (value) => typeof value === "string";
965
1061
  const isOneOf = (values, value) => values.some((candidate) => candidate === value);
966
1062
  /** Reads an option whose choices commander has already checked, narrowing it to its type. */
967
1063
  const choice = (values, value, name) => {
968
1064
  if (!isOneOf(values, value)) throw new Error(`--${name} was not checked against its choices`);
969
1065
  return value;
970
1066
  };
1067
+ /** The word that forgets a named fact rather than giving it a value. */
1068
+ const unknownValue = "unknown";
1069
+ /** A number as JSON writes one, so a value that reads as a number is never also meant as text. */
1070
+ const jsonNumber = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u;
1071
+ /**
1072
+ * Reads a fact's value from the command line: `true` and `false` are booleans, `unknown` forgets the fact, a JSON number is a number, and anything else is text; `--text` reads the value as text whatever it looks like.
1073
+ * @param value - The value as given.
1074
+ * @param asText - Whether `--text` was given.
1075
+ * @returns The trilean value, or `unknown`.
1076
+ */
1077
+ const factValueOf = (value, asText) => {
1078
+ if (asText) return {
1079
+ kind: "text",
1080
+ value
1081
+ };
1082
+ if (value === unknownValue) return unknownValue;
1083
+ if (value === "true" || value === "false") return {
1084
+ kind: "boolean",
1085
+ value: value === "true"
1086
+ };
1087
+ return jsonNumber.test(value) ? {
1088
+ kind: "number",
1089
+ value: Number(value)
1090
+ } : {
1091
+ kind: "text",
1092
+ value
1093
+ };
1094
+ };
971
1095
  /**
972
1096
  * Runs the command line with the given arguments, writing only through the ports, and resolves with the exit status. It never exits the process or reads its arguments itself, so a test or a host program such as Switchboard runs it as a function.
973
1097
  * @param args - The arguments after the program name, such as `["ready", "--json"]`.
@@ -1131,15 +1255,17 @@ const runCli = async (args, ports, identity) => {
1131
1255
  }
1132
1256
  }, "participant");
1133
1257
  });
1134
- addCommand.command("activity").description("add an activity: a step of work").argument("<id>", "its identifier").argument("<label>", "its label").option("--actor <participant>", "the role or system that does it").action(async (id, label, options, command) => {
1135
- const { actor } = options;
1258
+ addCommand.command("activity").description("add an activity: a step of work").argument("<id>", "its identifier").argument("<label>", "its label").option("--actor <participant>", "the role or system that does it").option("--consumes <artefact...>", "the artefacts it needs before it can start").option("--produces <artefact...>", "the artefacts it makes").action(async (id, label, options, command) => {
1259
+ const { actor, consumes, produces } = options;
1136
1260
  await added(command, {
1137
1261
  kind: "element",
1138
1262
  record: {
1139
1263
  kind: "activity",
1140
1264
  id,
1141
1265
  label,
1142
- ...typeof actor === "string" ? { actor } : {}
1266
+ ...typeof actor === "string" ? { actor } : {},
1267
+ ...Array.isArray(consumes) ? { consumes: consumes.filter(isText) } : {},
1268
+ ...Array.isArray(produces) ? { produces: produces.filter(isText) } : {}
1143
1269
  }
1144
1270
  }, "activity");
1145
1271
  });
@@ -1202,7 +1328,7 @@ const runCli = async (args, ports, identity) => {
1202
1328
  }
1203
1329
  }, "transition");
1204
1330
  });
1205
- program.command("set").description("set an element's run state").argument("<id>", "the element").addArgument(new Argument("<state>", "its run state").choices(overlayStatuses)).action(async (id, state, _options, command) => {
1331
+ const setCommand = program.command("set").description("set an element's run state; set artefact and set fact record what is known from outside the process").argument("<id>", "the element").addArgument(new Argument("<state>", "its run state").choices(overlayStatuses)).action(async (id, state, _options, command) => {
1206
1332
  const status = choice(overlayStatuses, state, "state");
1207
1333
  await withSession(async (session) => {
1208
1334
  const result = await setState(session, id, status);
@@ -1212,6 +1338,35 @@ const runCli = async (args, ports, identity) => {
1212
1338
  };
1213
1339
  })(command);
1214
1340
  });
1341
+ setCommand.command("artefact").description("record whether an artefact is provided, so work consuming it can start; unknown forgets it").argument("<id>", "the artefact").addArgument(new Argument("<state>", "whether it is provided").choices(artefactStates)).action(async (id, state, _options, command) => {
1342
+ const provision = choice(artefactStates, state, "state");
1343
+ await withSession(async (session) => {
1344
+ const result = await setArtefact(session, id, provision);
1345
+ return {
1346
+ json: result,
1347
+ text: renderSetArtefact(result)
1348
+ };
1349
+ })(command);
1350
+ });
1351
+ setCommand.command("fact").description("record the value of a named fact that decision conditions read; unknown forgets it").argument("<name>", "the fact's name, as a condition's reference gives it").argument("<value>", "true, false, unknown, a number, or any other text").option("--text", "read the value as text, even true, false or a number").action(async (name, value, options, command) => {
1352
+ const parsed = factValueOf(value, options.text === true);
1353
+ await withSession(async (session) => {
1354
+ const result = await setFact(session, name, parsed);
1355
+ return {
1356
+ json: result,
1357
+ text: renderSetFact(result)
1358
+ };
1359
+ })(command);
1360
+ });
1361
+ program.command("facts").description("list what is recorded about the run: each artefact provided or absent, and each named fact's value").action(async (_options, command) => {
1362
+ await withSession(async (session) => {
1363
+ const result = await factsOf(session);
1364
+ return {
1365
+ json: result,
1366
+ text: renderFacts(result)
1367
+ };
1368
+ })(command);
1369
+ });
1215
1370
  program.command("validate").description("check the stored model, or a model file, for problems; exits with the invalid model status when there are any").argument("[file]", "a model as JSON, checked instead of the stored model").action(async (file, _options, command) => {
1216
1371
  const outcome = (result) => ({
1217
1372
  json: result,
@@ -1385,4 +1540,4 @@ const runProcess = async (identity) => {
1385
1540
  return runCli(process.argv.slice(2), nodePorts({ signal: controller.signal }), identity);
1386
1541
  };
1387
1542
  //#endregion
1388
- export { add as A, blocked as C, path as D, impactOf as E, failureOf as F, findRun as M, loadRun as N, ready as O, CliError as P, swimlaneToMermaid as S, handoffsOf as T, stateMachineToDot as _, defaultModelId as a, sequenceToMermaid as b, validateStored as c, formatNames as d, project as f, sequenceToDot as g, flowchartToDot as h, runProcess as i, setState as j, why as k, sync as l, viewNames as m, nodePorts as n, runCli as o, show as p, packageVersion as r, validateDocument as s, defaultDataPath as t, emit as u, swimlaneToDot as v, cycles as w, stateMachineToMermaid as x, flowchartToMermaid as y };
1543
+ export { add as A, failureOf as B, blocked as C, path as D, impactOf as E, setState as F, findRun as I, loadRun as L, factsOf as M, setArtefact as N, ready as O, setFact as P, runFacts as R, swimlaneToMermaid as S, handoffsOf as T, stateMachineToDot as _, defaultModelId as a, sequenceToMermaid as b, validateStored as c, formatNames as d, project as f, sequenceToDot as g, flowchartToDot as h, runProcess as i, artefactStates as j, why as k, sync as l, viewNames as m, nodePorts as n, runCli as o, show as p, packageVersion as r, validateDocument as s, defaultDataPath as t, emit as u, swimlaneToDot as v, cycles as w, stateMachineToMermaid as x, flowchartToMermaid as y, CliError as z };
@@ -0,0 +1,3 @@
1
+ import "./exit-codes-0ultCqdS.js";
2
+ import { i as runProcess, r as packageVersion } from "./dist-BxHnxpcT.js";
3
+ export { packageVersion, runProcess };