@decionis/agent-safe-pipeline 0.4.1 → 0.5.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.
Files changed (75) hide show
  1. package/README.md +10 -0
  2. package/dist/Index.d.ts +6 -0
  3. package/dist/Index.d.ts.map +1 -1
  4. package/dist/Index.js +6 -0
  5. package/dist/Index.js.map +1 -1
  6. package/dist/execution/ActionRegistry.d.ts +12 -0
  7. package/dist/execution/ActionRegistry.d.ts.map +1 -1
  8. package/dist/execution/ActionRegistry.js +14 -0
  9. package/dist/execution/ActionRegistry.js.map +1 -1
  10. package/dist/execution/GovernedExecutionRunner.d.ts +8 -0
  11. package/dist/execution/GovernedExecutionRunner.d.ts.map +1 -1
  12. package/dist/execution/GovernedExecutionRunner.js +28 -0
  13. package/dist/execution/GovernedExecutionRunner.js.map +1 -1
  14. package/dist/execution/NativeExecutor.d.ts +13 -0
  15. package/dist/execution/NativeExecutor.d.ts.map +1 -1
  16. package/dist/execution/NativeExecutor.js.map +1 -1
  17. package/dist/intent/ExecutionIntent.d.ts +11 -0
  18. package/dist/intent/ExecutionIntent.d.ts.map +1 -1
  19. package/dist/intent/ExecutionIntent.js +15 -0
  20. package/dist/intent/ExecutionIntent.js.map +1 -1
  21. package/dist/intent/ExecutionSignals.d.ts +9 -1
  22. package/dist/intent/ExecutionSignals.d.ts.map +1 -1
  23. package/dist/intent/ExecutionSignals.js +9 -0
  24. package/dist/intent/ExecutionSignals.js.map +1 -1
  25. package/dist/intent/IntentCapture.d.ts.map +1 -1
  26. package/dist/intent/IntentCapture.js +15 -4
  27. package/dist/intent/IntentCapture.js.map +1 -1
  28. package/dist/intent/TargetPrecondition.d.ts +36 -0
  29. package/dist/intent/TargetPrecondition.d.ts.map +1 -0
  30. package/dist/intent/TargetPrecondition.js +57 -0
  31. package/dist/intent/TargetPrecondition.js.map +1 -0
  32. package/dist/manifest/ActionManifest.d.ts +112 -0
  33. package/dist/manifest/ActionManifest.d.ts.map +1 -0
  34. package/dist/manifest/ActionManifest.js +182 -0
  35. package/dist/manifest/ActionManifest.js.map +1 -0
  36. package/dist/plan/GovernedPlan.d.ts +109 -0
  37. package/dist/plan/GovernedPlan.d.ts.map +1 -0
  38. package/dist/plan/GovernedPlan.js +187 -0
  39. package/dist/plan/GovernedPlan.js.map +1 -0
  40. package/dist/plan/PlanState.d.ts +94 -0
  41. package/dist/plan/PlanState.d.ts.map +1 -0
  42. package/dist/plan/PlanState.js +240 -0
  43. package/dist/plan/PlanState.js.map +1 -0
  44. package/dist/plan/PlanStepReference.d.ts +16 -0
  45. package/dist/plan/PlanStepReference.d.ts.map +1 -0
  46. package/dist/plan/PlanStepReference.js +25 -0
  47. package/dist/plan/PlanStepReference.js.map +1 -0
  48. package/dist/profile/ApiProfile.d.ts +202 -2
  49. package/dist/profile/ApiProfile.d.ts.map +1 -1
  50. package/dist/profile/ApiProfile.js +397 -11
  51. package/dist/profile/ApiProfile.js.map +1 -1
  52. package/dist/profile/BuiltInApiProfiles.d.ts.map +1 -1
  53. package/dist/profile/BuiltInApiProfiles.js +199 -1
  54. package/dist/profile/BuiltInApiProfiles.js.map +1 -1
  55. package/dist/profile/McpToolProfile.d.ts +129 -0
  56. package/dist/profile/McpToolProfile.d.ts.map +1 -0
  57. package/dist/profile/McpToolProfile.js +270 -0
  58. package/dist/profile/McpToolProfile.js.map +1 -0
  59. package/dist/testing/Index.d.ts +3 -1
  60. package/dist/testing/Index.d.ts.map +1 -1
  61. package/dist/testing/Index.js +2 -0
  62. package/dist/testing/Index.js.map +1 -1
  63. package/dist/testing/LocalAssumptionBreaches.d.ts +96 -0
  64. package/dist/testing/LocalAssumptionBreaches.d.ts.map +1 -0
  65. package/dist/testing/LocalAssumptionBreaches.js +109 -0
  66. package/dist/testing/LocalAssumptionBreaches.js.map +1 -0
  67. package/dist/testing/LocalAuthority.d.ts +152 -5
  68. package/dist/testing/LocalAuthority.d.ts.map +1 -1
  69. package/dist/testing/LocalAuthority.js +294 -18
  70. package/dist/testing/LocalAuthority.js.map +1 -1
  71. package/dist/testing/LocalPlans.d.ts +43 -0
  72. package/dist/testing/LocalPlans.d.ts.map +1 -0
  73. package/dist/testing/LocalPlans.js +124 -0
  74. package/dist/testing/LocalPlans.js.map +1 -0
  75. package/package.json +2 -2
@@ -0,0 +1,109 @@
1
+ import { z } from "zod";
2
+ import type { AgentProposal } from "../intent/ExecutionIntent.js";
3
+ import { type JsonObject } from "../intent/JsonValue.js";
4
+ import { type PlanStepReference } from "./PlanStepReference.js";
5
+ /**
6
+ * A governed plan: one change carried out as several governed operations,
7
+ * each proposed, decided, granted, claimed and finalized on its own.
8
+ *
9
+ * The plan is a governance object, not an orchestrator. It names the steps,
10
+ * which step depends on which and how (the coupling), and what stays
11
+ * admissible once a step has failed. Registering it with the authority
12
+ * authorizes no step: the authority derives each step's plan facts from its
13
+ * own finalization records, and policy decides each step with those facts in
14
+ * front of it. ADR 0008 is the design.
15
+ */
16
+ export declare const GOVERNED_PLAN_VERSION = "agent-safe.plan/1";
17
+ /** What a predecessor's outcome means for the step that depends on it. */
18
+ export declare const PLAN_COUPLINGS: readonly ["independent", "ordered", "all_or_escalate"];
19
+ export declare const MAX_PLAN_STEPS = 32;
20
+ export declare const MAX_PLAN_EDGES = 64;
21
+ export declare const PlanStepSchema: z.ZodObject<{
22
+ step_id: z.ZodString;
23
+ action: z.ZodString;
24
+ target: z.ZodString;
25
+ parameters: z.ZodOptional<z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>>;
26
+ compensates: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ }, z.core.$strict>;
28
+ export declare const PlanEdgeSchema: z.ZodObject<{
29
+ from: z.ZodString;
30
+ to: z.ZodString;
31
+ coupling: z.ZodEnum<{
32
+ independent: "independent";
33
+ ordered: "ordered";
34
+ all_or_escalate: "all_or_escalate";
35
+ }>;
36
+ }, z.core.$strict>;
37
+ /** What stays admissible once a step has FAILED. */
38
+ export declare const PartialCommitRuleSchema: z.ZodObject<{
39
+ admissible: z.ZodArray<z.ZodString>;
40
+ otherwise: z.ZodEnum<{
41
+ ESCALATE: "ESCALATE";
42
+ BLOCK: "BLOCK";
43
+ }>;
44
+ }, z.core.$strict>;
45
+ export declare const GovernedPlanSchema: z.ZodObject<{
46
+ version: z.ZodLiteral<"agent-safe.plan/1">;
47
+ plan_id: z.ZodString;
48
+ tenant_id: z.ZodString;
49
+ steps: z.ZodArray<z.ZodObject<{
50
+ step_id: z.ZodString;
51
+ action: z.ZodString;
52
+ target: z.ZodString;
53
+ parameters: z.ZodOptional<z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>>;
54
+ compensates: z.ZodOptional<z.ZodArray<z.ZodString>>;
55
+ }, z.core.$strict>>;
56
+ edges: z.ZodArray<z.ZodObject<{
57
+ from: z.ZodString;
58
+ to: z.ZodString;
59
+ coupling: z.ZodEnum<{
60
+ independent: "independent";
61
+ ordered: "ordered";
62
+ all_or_escalate: "all_or_escalate";
63
+ }>;
64
+ }, z.core.$strict>>;
65
+ partial_commit: z.ZodObject<{
66
+ admissible: z.ZodArray<z.ZodString>;
67
+ otherwise: z.ZodEnum<{
68
+ ESCALATE: "ESCALATE";
69
+ BLOCK: "BLOCK";
70
+ }>;
71
+ }, z.core.$strict>;
72
+ }, z.core.$strict>;
73
+ export type PlanCoupling = (typeof PLAN_COUPLINGS)[number];
74
+ export type PlanStep = z.infer<typeof PlanStepSchema>;
75
+ export type PlanEdge = z.infer<typeof PlanEdgeSchema>;
76
+ export type PartialCommitRule = z.infer<typeof PartialCommitRuleSchema>;
77
+ export type GovernedPlan = z.infer<typeof GovernedPlanSchema>;
78
+ /**
79
+ * The plan's digest: SHA-256 over its canonical JSON (sorted keys), the form
80
+ * every digest in the intent contract uses. The plan is parsed first, so two
81
+ * spellings of one plan that the schema accepts differ only where the plan
82
+ * itself differs.
83
+ */
84
+ export declare function planDigest(plan: GovernedPlan): `sha256:${string}`;
85
+ export type PlanStepMismatch = "PLAN_STEP_UNKNOWN" | "PLAN_STEP_ACTION_MISMATCH" | "PLAN_STEP_TARGET_OUT_OF_SCOPE" | "PLAN_STEP_PARAMETER_MISMATCH";
86
+ export type PlanStepMatch = {
87
+ readonly ok: true;
88
+ readonly reference: PlanStepReference;
89
+ } | {
90
+ readonly ok: false;
91
+ readonly code: PlanStepMismatch;
92
+ };
93
+ /** Whether a target is inside a step's scope: exact, or under a trailing-`*` prefix. */
94
+ export declare function planTargetInScope(scope: string, target: string): boolean;
95
+ /**
96
+ * Matches a proposal to one step of a plan and, when it fits, returns the
97
+ * reference the trusted executor stamps into the intent. A different action,
98
+ * a target outside the step's scope, or a parameter that differs from one the
99
+ * step pins is refused here, before anything is captured. The authority makes
100
+ * the same match itself; this one only fails fast.
101
+ */
102
+ export declare function matchPlanStep(plan: GovernedPlan, stepId: string, proposal: AgentProposal): PlanStepMatch;
103
+ /** The reason an action is outside a step, or null when it fits. */
104
+ export declare function planStepMismatch(step: PlanStep, action: {
105
+ readonly action: string;
106
+ readonly target: string;
107
+ readonly parameters: JsonObject;
108
+ }): Exclude<PlanStepMismatch, "PLAN_STEP_UNKNOWN"> | null;
109
+ //# sourceMappingURL=GovernedPlan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GovernedPlan.d.ts","sourceRoot":"","sources":["../../src/plan/GovernedPlan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAoB,KAAK,UAAU,EAAkB,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAkC,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD,0EAA0E;AAC1E,eAAO,MAAM,cAAc,wDAAyD,CAAC;AAErF,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,cAAc,KAAK,CAAC;AAiBjC,eAAO,MAAM,cAAc;;;;;;kBAUhB,CAAC;AAEZ,eAAO,MAAM,cAAc;;;;;;;;kBAMhB,CAAC;AAEZ,oDAAoD;AACpD,eAAO,MAAM,uBAAuB;;;;;;kBAKzB,CAAC;AAEZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAa3B,CAAC;AAEL,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAyD9D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU,MAAM,EAAE,CAIjE;AAED,MAAM,MAAM,gBAAgB,GACxB,mBAAmB,GACnB,2BAA2B,GAC3B,+BAA+B,GAC/B,8BAA8B,CAAC;AAEnC,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE5D,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,aAAa,GACtB,aAAa,CAaf;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;CAAE,GAC5F,OAAO,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,IAAI,CAavD"}
@@ -0,0 +1,187 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { CanonicalIntentHasher } from "../intent/CanonicalIntentHasher.js";
4
+ import { JsonObjectSchema } from "../intent/JsonValue.js";
5
+ import { PlanIdSchema, PlanStepIdSchema } from "./PlanStepReference.js";
6
+ /**
7
+ * A governed plan: one change carried out as several governed operations,
8
+ * each proposed, decided, granted, claimed and finalized on its own.
9
+ *
10
+ * The plan is a governance object, not an orchestrator. It names the steps,
11
+ * which step depends on which and how (the coupling), and what stays
12
+ * admissible once a step has failed. Registering it with the authority
13
+ * authorizes no step: the authority derives each step's plan facts from its
14
+ * own finalization records, and policy decides each step with those facts in
15
+ * front of it. ADR 0008 is the design.
16
+ */
17
+ export const GOVERNED_PLAN_VERSION = "agent-safe.plan/1";
18
+ /** What a predecessor's outcome means for the step that depends on it. */
19
+ export const PLAN_COUPLINGS = ["independent", "ordered", "all_or_escalate"];
20
+ export const MAX_PLAN_STEPS = 32;
21
+ export const MAX_PLAN_EDGES = 64;
22
+ const MAX_COMPENSATED_STEPS = 8;
23
+ const actionName = z
24
+ .string()
25
+ .min(1)
26
+ .max(120)
27
+ .regex(/^[a-z][a-z0-9._:-]*$/);
28
+ /** An exact target, or a prefix followed by one trailing `*`. */
29
+ const targetScope = z
30
+ .string()
31
+ .min(1)
32
+ .max(500)
33
+ .refine((value) => !value.slice(0, -1).includes("*") && value !== "*", {
34
+ message: "PLAN_TARGET_SCOPE_INVALID",
35
+ });
36
+ export const PlanStepSchema = z
37
+ .object({
38
+ step_id: PlanStepIdSchema,
39
+ action: actionName,
40
+ target: targetScope,
41
+ /** Parameters the step pins: each must equal the proposal's, canonically. */
42
+ parameters: JsonObjectSchema.optional(),
43
+ /** Present only on a compensation step: the steps whose failure it may follow. */
44
+ compensates: z.array(PlanStepIdSchema).min(1).max(MAX_COMPENSATED_STEPS).optional(),
45
+ })
46
+ .strict();
47
+ export const PlanEdgeSchema = z
48
+ .object({
49
+ from: PlanStepIdSchema,
50
+ to: PlanStepIdSchema,
51
+ coupling: z.enum(PLAN_COUPLINGS),
52
+ })
53
+ .strict();
54
+ /** What stays admissible once a step has FAILED. */
55
+ export const PartialCommitRuleSchema = z
56
+ .object({
57
+ admissible: z.array(PlanStepIdSchema).max(MAX_PLAN_STEPS),
58
+ otherwise: z.enum(["ESCALATE", "BLOCK"]),
59
+ })
60
+ .strict();
61
+ export const GovernedPlanSchema = z
62
+ .object({
63
+ version: z.literal(GOVERNED_PLAN_VERSION),
64
+ plan_id: PlanIdSchema,
65
+ tenant_id: z.string().uuid(),
66
+ steps: z.array(PlanStepSchema).min(1).max(MAX_PLAN_STEPS),
67
+ edges: z.array(PlanEdgeSchema).max(MAX_PLAN_EDGES),
68
+ partial_commit: PartialCommitRuleSchema,
69
+ })
70
+ .strict()
71
+ .superRefine((plan, issues) => {
72
+ const problem = planStructureProblem(plan);
73
+ if (problem !== null)
74
+ issues.addIssue({ code: "custom", message: problem });
75
+ });
76
+ /**
77
+ * The first structural problem with a plan, or null. The checks are the ones
78
+ * a dependency graph needs to mean anything: every edge joins two known,
79
+ * distinct steps once; there is no cycle; and a compensation step stands
80
+ * outside the graph, compensating ordinary steps only.
81
+ */
82
+ function planStructureProblem(plan) {
83
+ const steps = new Map();
84
+ for (const step of plan.steps) {
85
+ if (steps.has(step.step_id))
86
+ return "PLAN_STEP_DUPLICATE";
87
+ steps.set(step.step_id, step);
88
+ }
89
+ const isCompensation = (id) => steps.get(id)?.compensates !== undefined;
90
+ for (const step of plan.steps) {
91
+ for (const compensated of step.compensates ?? []) {
92
+ if (!steps.has(compensated) || isCompensation(compensated)) {
93
+ return "PLAN_COMPENSATION_INVALID";
94
+ }
95
+ }
96
+ }
97
+ const seen = new Set();
98
+ const successors = new Map();
99
+ for (const edge of plan.edges) {
100
+ if (!steps.has(edge.from) || !steps.has(edge.to))
101
+ return "PLAN_EDGE_UNKNOWN_STEP";
102
+ if (edge.from === edge.to)
103
+ return "PLAN_EDGE_SELF";
104
+ if (isCompensation(edge.from) || isCompensation(edge.to))
105
+ return "PLAN_COMPENSATION_INVALID";
106
+ const key = `${edge.from}\n${edge.to}`;
107
+ if (seen.has(key))
108
+ return "PLAN_EDGE_DUPLICATE";
109
+ seen.add(key);
110
+ successors.set(edge.from, [...(successors.get(edge.from) ?? []), edge.to]);
111
+ }
112
+ for (const id of plan.partial_commit.admissible) {
113
+ if (!steps.has(id) || isCompensation(id))
114
+ return "PLAN_PARTIAL_UNKNOWN_STEP";
115
+ }
116
+ // Kahn's algorithm: a graph with a cycle leaves steps no order can reach.
117
+ const indegree = new Map([...steps.keys()].map((id) => [id, 0]));
118
+ for (const edge of plan.edges)
119
+ indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1);
120
+ const ready = [...indegree].filter(([, count]) => count === 0).map(([id]) => id);
121
+ let ordered = 0;
122
+ while (ready.length > 0) {
123
+ const id = ready.pop();
124
+ ordered += 1;
125
+ for (const next of successors.get(id) ?? []) {
126
+ const remaining = (indegree.get(next) ?? 0) - 1;
127
+ indegree.set(next, remaining);
128
+ if (remaining === 0)
129
+ ready.push(next);
130
+ }
131
+ }
132
+ return ordered === steps.size ? null : "PLAN_CYCLE";
133
+ }
134
+ /**
135
+ * The plan's digest: SHA-256 over its canonical JSON (sorted keys), the form
136
+ * every digest in the intent contract uses. The plan is parsed first, so two
137
+ * spellings of one plan that the schema accepts differ only where the plan
138
+ * itself differs.
139
+ */
140
+ export function planDigest(plan) {
141
+ const parsed = GovernedPlanSchema.parse(plan);
142
+ const canonical = CanonicalIntentHasher.stringify(parsed);
143
+ return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
144
+ }
145
+ /** Whether a target is inside a step's scope: exact, or under a trailing-`*` prefix. */
146
+ export function planTargetInScope(scope, target) {
147
+ return scope.endsWith("*") ? target.startsWith(scope.slice(0, -1)) : target === scope;
148
+ }
149
+ /**
150
+ * Matches a proposal to one step of a plan and, when it fits, returns the
151
+ * reference the trusted executor stamps into the intent. A different action,
152
+ * a target outside the step's scope, or a parameter that differs from one the
153
+ * step pins is refused here, before anything is captured. The authority makes
154
+ * the same match itself; this one only fails fast.
155
+ */
156
+ export function matchPlanStep(plan, stepId, proposal) {
157
+ const step = plan.steps.find((candidate) => candidate.step_id === stepId);
158
+ if (step === undefined)
159
+ return { ok: false, code: "PLAN_STEP_UNKNOWN" };
160
+ const mismatch = planStepMismatch(step, {
161
+ action: proposal.action,
162
+ target: proposal.target,
163
+ parameters: proposal.parameters,
164
+ });
165
+ if (mismatch !== null)
166
+ return { ok: false, code: mismatch };
167
+ return {
168
+ ok: true,
169
+ reference: { plan_id: plan.plan_id, plan_digest: planDigest(plan), step_id: step.step_id },
170
+ };
171
+ }
172
+ /** The reason an action is outside a step, or null when it fits. */
173
+ export function planStepMismatch(step, action) {
174
+ if (action.action !== step.action)
175
+ return "PLAN_STEP_ACTION_MISMATCH";
176
+ if (!planTargetInScope(step.target, action.target))
177
+ return "PLAN_STEP_TARGET_OUT_OF_SCOPE";
178
+ for (const [name, pinned] of Object.entries(step.parameters ?? {})) {
179
+ const proposed = action.parameters[name];
180
+ if (proposed === undefined ||
181
+ CanonicalIntentHasher.stringify(proposed) !== CanonicalIntentHasher.stringify(pinned)) {
182
+ return "PLAN_STEP_PARAMETER_MISMATCH";
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ //# sourceMappingURL=GovernedPlan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GovernedPlan.js","sourceRoot":"","sources":["../../src/plan/GovernedPlan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import type { JsonObject } from "../intent/JsonValue.js";
2
+ import { type GovernedPlan, type PlanCoupling } from "./GovernedPlan.js";
3
+ import type { PlanStepReference } from "./PlanStepReference.js";
4
+ /**
5
+ * The facts an authority derives for one step of a governed plan, from its
6
+ * own finalization records and nothing a caller says. This is the reference
7
+ * derivation: Decionis mirrors it, and the two are held to the same cases.
8
+ *
9
+ * Every fact is a function of the plan and the records, so a decision and the
10
+ * claim that follows it can be compared: the claim re-derives the step's view
11
+ * and refuses when it is not the one the decision rested on (ADR 0008).
12
+ */
13
+ /** One attempt at a step, as a finalization record reads: claimed, then settled. */
14
+ export interface PlanStepAttempt {
15
+ readonly step_id: string;
16
+ /** Null while the claim is open. */
17
+ readonly outcome: "COMMITTED" | "FAILED" | "INDETERMINATE" | null;
18
+ /** The effect evidence's status at finalization; null when none was recorded. */
19
+ readonly effect: "CONFIRMED" | "UNCONFIRMED" | null;
20
+ /** When the record last changed: the claim, or its settlement. */
21
+ readonly recorded_at: string;
22
+ }
23
+ export declare const PLAN_STEP_STATUSES: readonly ["PENDING", "IN_FLIGHT", "COMMITTED", "UNCONFIRMED", "INDETERMINATE", "FAILED"];
24
+ export declare const PLAN_STATES: readonly ["ACTIVE", "PARTIAL", "SUSPENDED", "COMPLETE"];
25
+ export declare const PLAN_ADMISSIBILITY: readonly ["ADMISSIBLE", "COMPENSATION", "ESCALATE", "WAITING", "INADMISSIBLE"];
26
+ export declare const PLAN_REASON_CODES: readonly ["PLAN_UNKNOWN", "PLAN_DIGEST_MISMATCH", "STEP_UNKNOWN", "STEP_MISMATCH", "PLAN_SUSPENDED", "PLAN_COMPLETE", "STEP_ALREADY_ATTEMPTED", "NOTHING_TO_COMPENSATE", "COMPENSATION_ADMISSIBLE", "PREDECESSOR_FAILED", "PREDECESSOR_NOT_COMMITTED", "PREDECESSOR_UNCONFIRMED", "PREDECESSOR_FAILED_ESCALATE", "PLAN_PARTIAL_ADMISSIBLE", "PLAN_PARTIAL_ESCALATE", "PLAN_PARTIAL_BLOCK", "STEP_ADMISSIBLE"];
27
+ export type PlanStepStatus = (typeof PLAN_STEP_STATUSES)[number];
28
+ export type PlanState = (typeof PLAN_STATES)[number];
29
+ export type PlanAdmissibility = (typeof PLAN_ADMISSIBILITY)[number];
30
+ export type PlanReasonCode = (typeof PLAN_REASON_CODES)[number];
31
+ /** The prefix of every plan-step signal id; reserved, so no caller can bind one. */
32
+ export declare const PLAN_STEP_SIGNAL_PREFIX = "agent_safe.plan:";
33
+ /** A material signal reference, in the authority's binding shape. */
34
+ export interface PlanStepSignal {
35
+ readonly signal_id: string;
36
+ /** The digest of the step's view: what the decision rested on. */
37
+ readonly version: `sha256:${string}`;
38
+ readonly observed_at: string;
39
+ readonly value_digest: `sha256:${string}`;
40
+ readonly evidence_digest: null;
41
+ }
42
+ /** What policy reads under `agent_safe.plan`. */
43
+ export interface PlanStepFacts {
44
+ readonly schema_version: "1.0";
45
+ readonly plan_id: string;
46
+ readonly plan_digest: string;
47
+ readonly step_id: string;
48
+ readonly state: PlanState | null;
49
+ readonly admissibility: PlanAdmissibility;
50
+ readonly reason: PlanReasonCode;
51
+ readonly status: PlanStepStatus | null;
52
+ readonly predecessors: readonly {
53
+ readonly step_id: string;
54
+ readonly coupling: PlanCoupling;
55
+ readonly status: PlanStepStatus;
56
+ }[];
57
+ readonly compensates: readonly {
58
+ readonly step_id: string;
59
+ readonly status: PlanStepStatus;
60
+ }[];
61
+ readonly signal: PlanStepSignal | null;
62
+ }
63
+ /** An action as the authority sees it, to be matched against the step it claims to be. */
64
+ export interface PlanStepAction {
65
+ readonly action: string;
66
+ readonly target: string;
67
+ readonly parameters: JsonObject;
68
+ }
69
+ /** Each step's status, from every attempt the records hold. */
70
+ export declare function planStepStatuses(plan: GovernedPlan, attempts: readonly PlanStepAttempt[]): ReadonlyMap<string, PlanStepStatus>;
71
+ /**
72
+ * The plan's state. An unknown outcome suspends everything until it is
73
+ * reconciled; a failure makes the plan PARTIAL for good (it did not complete
74
+ * as approved, and compensating does not make it so); a plan whose every
75
+ * ordinary step committed is COMPLETE.
76
+ */
77
+ export declare function planState(plan: GovernedPlan, statuses: ReadonlyMap<string, PlanStepStatus>): PlanState;
78
+ /**
79
+ * Derives one step's facts. `plan` is the registered plan, or null when the
80
+ * organisation registered none under the stamped id; `action`, when given, is
81
+ * matched against the step. Nothing here throws on a caller's input: every
82
+ * way a stamp can be wrong is an `INADMISSIBLE` fact with its reason.
83
+ */
84
+ export declare function derivePlanStepFacts(input: {
85
+ readonly reference: PlanStepReference;
86
+ readonly plan: GovernedPlan | null;
87
+ readonly attempts: readonly PlanStepAttempt[];
88
+ readonly action?: PlanStepAction;
89
+ /** When the plan was registered: the view's time when no record is newer. */
90
+ readonly registered_at?: string;
91
+ }): PlanStepFacts;
92
+ /** The signal id a step's view is bound under. */
93
+ export declare function planStepSignalId(planId: string, stepId: string): string;
94
+ //# sourceMappingURL=PlanState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlanState.d.ts","sourceRoot":"","sources":["../../src/plan/PlanState.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,240 @@
1
+ import { createHash } from "node:crypto";
2
+ import { CanonicalIntentHasher } from "../intent/CanonicalIntentHasher.js";
3
+ import { planDigest, planStepMismatch, } from "./GovernedPlan.js";
4
+ export const PLAN_STEP_STATUSES = [
5
+ "PENDING",
6
+ "IN_FLIGHT",
7
+ "COMMITTED",
8
+ "UNCONFIRMED",
9
+ "INDETERMINATE",
10
+ "FAILED",
11
+ ];
12
+ export const PLAN_STATES = ["ACTIVE", "PARTIAL", "SUSPENDED", "COMPLETE"];
13
+ export const PLAN_ADMISSIBILITY = [
14
+ "ADMISSIBLE",
15
+ "COMPENSATION",
16
+ "ESCALATE",
17
+ "WAITING",
18
+ "INADMISSIBLE",
19
+ ];
20
+ export const PLAN_REASON_CODES = [
21
+ "PLAN_UNKNOWN",
22
+ "PLAN_DIGEST_MISMATCH",
23
+ "STEP_UNKNOWN",
24
+ "STEP_MISMATCH",
25
+ "PLAN_SUSPENDED",
26
+ "PLAN_COMPLETE",
27
+ "STEP_ALREADY_ATTEMPTED",
28
+ "NOTHING_TO_COMPENSATE",
29
+ "COMPENSATION_ADMISSIBLE",
30
+ "PREDECESSOR_FAILED",
31
+ "PREDECESSOR_NOT_COMMITTED",
32
+ "PREDECESSOR_UNCONFIRMED",
33
+ "PREDECESSOR_FAILED_ESCALATE",
34
+ "PLAN_PARTIAL_ADMISSIBLE",
35
+ "PLAN_PARTIAL_ESCALATE",
36
+ "PLAN_PARTIAL_BLOCK",
37
+ "STEP_ADMISSIBLE",
38
+ ];
39
+ /** The prefix of every plan-step signal id; reserved, so no caller can bind one. */
40
+ export const PLAN_STEP_SIGNAL_PREFIX = "agent_safe.plan:";
41
+ /**
42
+ * Precedence when a step has several attempts: a commit outranks anything
43
+ * still open, and anything open outranks a settled failure. In v1 a failed
44
+ * step is not attempted again, so the order only matters for grants that
45
+ * raced to the same step.
46
+ */
47
+ const STATUS_PRECEDENCE = [
48
+ "COMMITTED",
49
+ "UNCONFIRMED",
50
+ "IN_FLIGHT",
51
+ "INDETERMINATE",
52
+ "FAILED",
53
+ "PENDING",
54
+ ];
55
+ function attemptStatus(attempt) {
56
+ if (attempt.outcome === null)
57
+ return "IN_FLIGHT";
58
+ if (attempt.outcome === "COMMITTED") {
59
+ return attempt.effect === "CONFIRMED" ? "COMMITTED" : "UNCONFIRMED";
60
+ }
61
+ return attempt.outcome;
62
+ }
63
+ /** Each step's status, from every attempt the records hold. */
64
+ export function planStepStatuses(plan, attempts) {
65
+ const statuses = new Map(plan.steps.map((step) => [step.step_id, "PENDING"]));
66
+ for (const attempt of attempts) {
67
+ const current = statuses.get(attempt.step_id);
68
+ if (current === undefined)
69
+ continue;
70
+ const next = attemptStatus(attempt);
71
+ if (STATUS_PRECEDENCE.indexOf(next) < STATUS_PRECEDENCE.indexOf(current)) {
72
+ statuses.set(attempt.step_id, next);
73
+ }
74
+ }
75
+ return statuses;
76
+ }
77
+ /**
78
+ * The plan's state. An unknown outcome suspends everything until it is
79
+ * reconciled; a failure makes the plan PARTIAL for good (it did not complete
80
+ * as approved, and compensating does not make it so); a plan whose every
81
+ * ordinary step committed is COMPLETE.
82
+ */
83
+ export function planState(plan, statuses) {
84
+ const all = [...statuses.values()];
85
+ if (all.includes("INDETERMINATE"))
86
+ return "SUSPENDED";
87
+ if (all.includes("FAILED"))
88
+ return "PARTIAL";
89
+ const ordinary = plan.steps.filter((step) => step.compensates === undefined);
90
+ return ordinary.every((step) => statuses.get(step.step_id) === "COMMITTED")
91
+ ? "COMPLETE"
92
+ : "ACTIVE";
93
+ }
94
+ function edgeResult(coupling, status) {
95
+ if (coupling === "independent" || status === "COMMITTED")
96
+ return null;
97
+ if (status === "FAILED") {
98
+ return coupling === "ordered"
99
+ ? { admissibility: "INADMISSIBLE", reason: "PREDECESSOR_FAILED" }
100
+ : { admissibility: "ESCALATE", reason: "PREDECESSOR_FAILED_ESCALATE" };
101
+ }
102
+ if (status === "UNCONFIRMED") {
103
+ return { admissibility: "ESCALATE", reason: "PREDECESSOR_UNCONFIRMED" };
104
+ }
105
+ return { admissibility: "WAITING", reason: "PREDECESSOR_NOT_COMMITTED" };
106
+ }
107
+ /** Refusals outrank waiting, and waiting outranks a person's decision. */
108
+ const EDGE_PRECEDENCE = ["INADMISSIBLE", "WAITING", "ESCALATE"];
109
+ function sha256(value) {
110
+ const canonical = CanonicalIntentHasher.stringify(value);
111
+ return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
112
+ }
113
+ /**
114
+ * Derives one step's facts. `plan` is the registered plan, or null when the
115
+ * organisation registered none under the stamped id; `action`, when given, is
116
+ * matched against the step. Nothing here throws on a caller's input: every
117
+ * way a stamp can be wrong is an `INADMISSIBLE` fact with its reason.
118
+ */
119
+ export function derivePlanStepFacts(input) {
120
+ const { reference, plan } = input;
121
+ const refused = (reason) => ({
122
+ schema_version: "1.0",
123
+ plan_id: reference.plan_id,
124
+ plan_digest: reference.plan_digest,
125
+ step_id: reference.step_id,
126
+ state: null,
127
+ admissibility: "INADMISSIBLE",
128
+ reason,
129
+ status: null,
130
+ predecessors: [],
131
+ compensates: [],
132
+ signal: null,
133
+ });
134
+ if (plan === null || plan.plan_id !== reference.plan_id)
135
+ return refused("PLAN_UNKNOWN");
136
+ if (planDigest(plan) !== reference.plan_digest)
137
+ return refused("PLAN_DIGEST_MISMATCH");
138
+ const step = plan.steps.find((candidate) => candidate.step_id === reference.step_id);
139
+ if (step === undefined)
140
+ return refused("STEP_UNKNOWN");
141
+ if (input.action !== undefined && planStepMismatch(step, input.action) !== null) {
142
+ return refused("STEP_MISMATCH");
143
+ }
144
+ const statuses = planStepStatuses(plan, input.attempts);
145
+ const state = planState(plan, statuses);
146
+ const status = statuses.get(step.step_id) ?? "PENDING";
147
+ const predecessors = plan.edges
148
+ .filter((edge) => edge.to === step.step_id)
149
+ .map((edge) => ({
150
+ step_id: edge.from,
151
+ coupling: edge.coupling,
152
+ status: statuses.get(edge.from) ?? "PENDING",
153
+ }))
154
+ .sort((left, right) => (left.step_id < right.step_id ? -1 : 1));
155
+ const compensates = [...(step.compensates ?? [])]
156
+ .sort()
157
+ .map((id) => ({ step_id: id, status: statuses.get(id) ?? "PENDING" }));
158
+ const { admissibility, reason } = stepAdmissibility(plan, step, state, status, predecessors, compensates);
159
+ // The view: everything this step's admissibility reads, and nothing else,
160
+ // so a change elsewhere in the plan that cannot change this step does not
161
+ // make its grant stale. Plan state is in every view.
162
+ const view = {
163
+ plan_id: plan.plan_id,
164
+ plan_digest: reference.plan_digest,
165
+ step_id: step.step_id,
166
+ state,
167
+ status,
168
+ predecessors: predecessors.map((entry) => ({ ...entry })),
169
+ compensates: compensates.map((entry) => ({ ...entry })),
170
+ };
171
+ const viewSteps = new Set([
172
+ step.step_id,
173
+ ...predecessors.map((entry) => entry.step_id),
174
+ ...compensates.map((entry) => entry.step_id),
175
+ ]);
176
+ // The view's time is its newest record, so it is as stable as the view: a
177
+ // record elsewhere in the plan cannot move it, and one that changes the
178
+ // plan state changes the version instead.
179
+ const times = input.attempts
180
+ .filter((attempt) => viewSteps.has(attempt.step_id))
181
+ .map((attempt) => attempt.recorded_at)
182
+ .sort();
183
+ const observedAt = times.at(-1) ?? input.registered_at ?? new Date(0).toISOString();
184
+ const digest = sha256(view);
185
+ return {
186
+ schema_version: "1.0",
187
+ plan_id: plan.plan_id,
188
+ plan_digest: reference.plan_digest,
189
+ step_id: step.step_id,
190
+ state,
191
+ admissibility,
192
+ reason,
193
+ status,
194
+ predecessors,
195
+ compensates,
196
+ signal: {
197
+ signal_id: planStepSignalId(plan.plan_id, step.step_id),
198
+ version: digest,
199
+ observed_at: observedAt,
200
+ value_digest: digest,
201
+ evidence_digest: null,
202
+ },
203
+ };
204
+ }
205
+ /** The signal id a step's view is bound under. */
206
+ export function planStepSignalId(planId, stepId) {
207
+ return `${PLAN_STEP_SIGNAL_PREFIX}${planId}:${stepId}`;
208
+ }
209
+ function stepAdmissibility(plan, step, state, status, predecessors, compensates) {
210
+ if (state === "SUSPENDED")
211
+ return { admissibility: "INADMISSIBLE", reason: "PLAN_SUSPENDED" };
212
+ if (state === "COMPLETE")
213
+ return { admissibility: "INADMISSIBLE", reason: "PLAN_COMPLETE" };
214
+ if (status !== "PENDING") {
215
+ return { admissibility: "INADMISSIBLE", reason: "STEP_ALREADY_ATTEMPTED" };
216
+ }
217
+ if (step.compensates !== undefined) {
218
+ return compensates.some((entry) => entry.status === "FAILED")
219
+ ? { admissibility: "COMPENSATION", reason: "COMPENSATION_ADMISSIBLE" }
220
+ : { admissibility: "INADMISSIBLE", reason: "NOTHING_TO_COMPENSATE" };
221
+ }
222
+ const results = predecessors
223
+ .map((entry) => edgeResult(entry.coupling, entry.status))
224
+ .filter((result) => result !== null);
225
+ for (const admissibility of EDGE_PRECEDENCE) {
226
+ const found = results.find((result) => result.admissibility === admissibility);
227
+ if (found !== undefined)
228
+ return found;
229
+ }
230
+ if (state === "PARTIAL") {
231
+ if (plan.partial_commit.admissible.includes(step.step_id)) {
232
+ return { admissibility: "ADMISSIBLE", reason: "PLAN_PARTIAL_ADMISSIBLE" };
233
+ }
234
+ return plan.partial_commit.otherwise === "ESCALATE"
235
+ ? { admissibility: "ESCALATE", reason: "PLAN_PARTIAL_ESCALATE" }
236
+ : { admissibility: "INADMISSIBLE", reason: "PLAN_PARTIAL_BLOCK" };
237
+ }
238
+ return { admissibility: "ADMISSIBLE", reason: "STEP_ADMISSIBLE" };
239
+ }
240
+ //# sourceMappingURL=PlanState.js.map