sysprom 1.10.2 → 1.12.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.
@@ -16,6 +16,9 @@ declare const optsSchema: z.ZodObject<{
16
16
  rationale: z.ZodOptional<z.ZodString>;
17
17
  scope: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
18
  selected: z.ZodOptional<z.ZodString>;
19
+ decision: z.ZodOptional<z.ZodString>;
20
+ element: z.ZodOptional<z.ZodString>;
21
+ governedBy: z.ZodOptional<z.ZodString>;
19
22
  option: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
23
  }, z.core.$strip>;
21
24
  export declare const addCommand: CommandDef<typeof argsSchema, typeof optsSchema>;
@@ -14,6 +14,18 @@ const optsSchema = mutationOpts.extend({
14
14
  rationale: z.string().optional().describe("Decision rationale"),
15
15
  scope: z.array(z.string()).optional().describe("Change scope (repeatable)"),
16
16
  selected: z.string().optional().describe("Selected option ID"),
17
+ decision: z
18
+ .string()
19
+ .optional()
20
+ .describe("Decision ID this change implements (required for change nodes)"),
21
+ element: z
22
+ .string()
23
+ .optional()
24
+ .describe("Element ID this realisation implements (required for realisation nodes)"),
25
+ governedBy: z
26
+ .string()
27
+ .optional()
28
+ .describe("Invariant or policy ID this gate enforces (required for gate nodes)"),
17
29
  option: z
18
30
  .array(z.string())
19
31
  .optional()
@@ -77,7 +89,13 @@ export const addCommand = {
77
89
  });
78
90
  }
79
91
  try {
80
- const newDoc = addNodeOp({ doc, node });
92
+ const newDoc = addNodeOp({
93
+ doc,
94
+ node,
95
+ decisionId: opts.decision,
96
+ elementId: opts.element,
97
+ governedById: opts.governedBy,
98
+ });
81
99
  persistDoc(newDoc, loaded, opts);
82
100
  if (opts.json) {
83
101
  console.log(JSON.stringify(node, null, 2));
@@ -224,6 +224,7 @@ export const RELATIONSHIP_ENDPOINT_TYPES = {
224
224
  "element",
225
225
  "realisation",
226
226
  "stage",
227
+ "gate",
227
228
  "change",
228
229
  "policy",
229
230
  ],
@@ -1,7 +1,13 @@
1
1
  import * as z from "zod";
2
2
  /**
3
3
  * Add a node to a SysProM document. Returns a new document with the node appended.
4
+ *
5
+ * Certain node types require a companion relationship at creation time:
6
+ * - `change` requires `decisionId` → creates `implements` relationship (INV2)
7
+ * - `realisation` requires `elementId` → creates `implements` relationship (INV10)
8
+ * - `gate` requires `governedById` → creates `governed_by` relationship (INV8)
4
9
  * @throws {Error} If a node with the same ID already exists.
10
+ * @throws {Error} If a required relationship target is missing or has the wrong type.
5
11
  */
6
12
  export declare const addNodeOp: import("./define-operation.js").DefinedOperation<z.ZodObject<{
7
13
  doc: z.ZodObject<{
@@ -594,6 +600,9 @@ export declare const addNodeOp: import("./define-operation.js").DefinedOperation
594
600
  } | undefined;
595
601
  };
596
602
  };
603
+ decisionId: z.ZodOptional<z.ZodString>;
604
+ elementId: z.ZodOptional<z.ZodString>;
605
+ governedById: z.ZodOptional<z.ZodString>;
597
606
  }, z.core.$strip>, z.ZodObject<{
598
607
  $schema: z.ZodOptional<z.ZodString>;
599
608
  metadata: z.ZodOptional<z.ZodObject<{
@@ -1,25 +1,90 @@
1
1
  import * as z from "zod";
2
2
  import { defineOperation } from "./define-operation.js";
3
3
  import { SysProMDocument, Node } from "../schema.js";
4
+ /**
5
+ * Validate that a referenced node exists and has the expected type.
6
+ * @param doc - Document to search for the target node.
7
+ * @param doc.nodes - Array of nodes in the document.
8
+ * @param id - ID of the target node to resolve.
9
+ * @param expectedTypes - Allowed node types for the target.
10
+ * @param label - Human-readable label for error messages (e.g. "Decision").
11
+ * @returns The resolved node.
12
+ * @throws {Error} If the node does not exist or has an unexpected type.
13
+ * @example
14
+ * resolveTarget(doc, "D1", ["decision"], "Decision");
15
+ */
16
+ function resolveTarget(doc, id, expectedTypes, label) {
17
+ const target = doc.nodes.find((n) => n.id === id);
18
+ if (!target) {
19
+ throw new Error(`${label} not found: ${id}. The referenced node must exist before adding this node.`);
20
+ }
21
+ if (!expectedTypes.includes(target.type)) {
22
+ const typeList = expectedTypes.join(" or ");
23
+ const article = /^[aeiou]/i.test(typeList) ? "an" : "a";
24
+ throw new Error(`Node ${id} is not ${article} ${typeList} (type: ${target.type}).`);
25
+ }
26
+ return target;
27
+ }
4
28
  /**
5
29
  * Add a node to a SysProM document. Returns a new document with the node appended.
30
+ *
31
+ * Certain node types require a companion relationship at creation time:
32
+ * - `change` requires `decisionId` → creates `implements` relationship (INV2)
33
+ * - `realisation` requires `elementId` → creates `implements` relationship (INV10)
34
+ * - `gate` requires `governedById` → creates `governed_by` relationship (INV8)
6
35
  * @throws {Error} If a node with the same ID already exists.
36
+ * @throws {Error} If a required relationship target is missing or has the wrong type.
7
37
  */
8
38
  export const addNodeOp = defineOperation({
9
39
  name: "addNode",
10
- description: "Add a node to the document. Throws if the ID already exists.",
40
+ description: "Add a node to the document. Throws if the ID already exists. Change, realisation, and gate nodes require companion relationship targets.",
11
41
  input: z.object({
12
42
  doc: SysProMDocument,
13
43
  node: Node,
44
+ decisionId: z.string().optional(),
45
+ elementId: z.string().optional(),
46
+ governedById: z.string().optional(),
14
47
  }),
15
48
  output: SysProMDocument,
16
- fn({ doc, node }) {
49
+ fn({ doc, node, decisionId, elementId, governedById }) {
17
50
  if (doc.nodes.some((n) => n.id === node.id)) {
18
51
  throw new Error(`Node with ID '${node.id}' already exists.`);
19
52
  }
20
- return {
21
- ...doc,
22
- nodes: [...doc.nodes, node],
23
- };
53
+ const newNodes = [...doc.nodes, node];
54
+ const newRels = [...(doc.relationships ?? [])];
55
+ if (node.type === "change") {
56
+ if (!decisionId) {
57
+ throw new Error(`Adding a change requires a decisionId. Use --decision <ID> to link this change to its decision.`);
58
+ }
59
+ resolveTarget(doc, decisionId, ["decision"], "Decision");
60
+ newRels.push({
61
+ from: node.id,
62
+ to: decisionId,
63
+ type: "implements",
64
+ });
65
+ }
66
+ if (node.type === "realisation") {
67
+ if (!elementId) {
68
+ throw new Error(`Adding a realisation requires an elementId. Use --element <ID> to link this realisation to its element.`);
69
+ }
70
+ resolveTarget(doc, elementId, ["element"], "Element");
71
+ newRels.push({
72
+ from: node.id,
73
+ to: elementId,
74
+ type: "implements",
75
+ });
76
+ }
77
+ if (node.type === "gate") {
78
+ if (!governedById) {
79
+ throw new Error(`Adding a gate requires a governedById. Use --governed-by <ID> to link this gate to the invariant or policy it enforces.`);
80
+ }
81
+ resolveTarget(doc, governedById, ["invariant", "policy"], "Invariant/policy");
82
+ newRels.push({
83
+ from: node.id,
84
+ to: governedById,
85
+ type: "governed_by",
86
+ });
87
+ }
88
+ return { ...doc, nodes: newNodes, relationships: newRels };
24
89
  },
25
90
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sysprom",
3
- "version": "1.10.2",
3
+ "version": "1.12.0",
4
4
  "description": "SysProM — System Provenance Model CLI and library",
5
5
  "author": "ExaDev",
6
6
  "homepage": "https://exadev.github.io/SysProM",