@decionis/agent-safe-pipeline 0.1.3 → 0.1.4

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 (68) hide show
  1. package/README.md +318 -16
  2. package/dist/Index.d.ts +1 -0
  3. package/dist/Index.d.ts.map +1 -1
  4. package/dist/Index.js +1 -0
  5. package/dist/Index.js.map +1 -1
  6. package/dist/approval/PresenceApprovalCoordinator.d.ts +25 -0
  7. package/dist/approval/PresenceApprovalCoordinator.d.ts.map +1 -1
  8. package/dist/approval/PresenceApprovalCoordinator.js +54 -6
  9. package/dist/approval/PresenceApprovalCoordinator.js.map +1 -1
  10. package/dist/audit/AuditRecorder.d.ts +122 -0
  11. package/dist/audit/AuditRecorder.d.ts.map +1 -0
  12. package/dist/audit/AuditRecorder.js +312 -0
  13. package/dist/audit/AuditRecorder.js.map +1 -0
  14. package/dist/decision/DecionisGate.d.ts +91 -2
  15. package/dist/decision/DecionisGate.d.ts.map +1 -1
  16. package/dist/decision/DecionisGate.js +793 -44
  17. package/dist/decision/DecionisGate.js.map +1 -1
  18. package/dist/decision/DecisionAuthority.d.ts +50 -1
  19. package/dist/decision/DecisionAuthority.d.ts.map +1 -1
  20. package/dist/decision/DecisionAuthority.js.map +1 -1
  21. package/dist/decision/FixtureDecisionAuthority.d.ts +27 -1
  22. package/dist/decision/FixtureDecisionAuthority.d.ts.map +1 -1
  23. package/dist/decision/FixtureDecisionAuthority.js +47 -0
  24. package/dist/decision/FixtureDecisionAuthority.js.map +1 -1
  25. package/dist/decision/ImmutableGateDecision.d.ts.map +1 -1
  26. package/dist/decision/ImmutableGateDecision.js +18 -0
  27. package/dist/decision/ImmutableGateDecision.js.map +1 -1
  28. package/dist/execution/ActionRegistry.d.ts +37 -0
  29. package/dist/execution/ActionRegistry.d.ts.map +1 -1
  30. package/dist/execution/ActionRegistry.js +75 -1
  31. package/dist/execution/ActionRegistry.js.map +1 -1
  32. package/dist/execution/AuthorizationVerifier.d.ts +29 -0
  33. package/dist/execution/AuthorizationVerifier.d.ts.map +1 -1
  34. package/dist/execution/AuthorizationVerifier.js +126 -25
  35. package/dist/execution/AuthorizationVerifier.js.map +1 -1
  36. package/dist/execution/SafeExecutor.d.ts +72 -7
  37. package/dist/execution/SafeExecutor.d.ts.map +1 -1
  38. package/dist/execution/SafeExecutor.js +253 -17
  39. package/dist/execution/SafeExecutor.js.map +1 -1
  40. package/dist/intent/CanonicalIntentHasher.d.ts +1 -1
  41. package/dist/intent/CanonicalIntentHasher.d.ts.map +1 -1
  42. package/dist/intent/CanonicalIntentHasher.js +6 -2
  43. package/dist/intent/CanonicalIntentHasher.js.map +1 -1
  44. package/dist/intent/ExecutionIntent.d.ts +14 -2
  45. package/dist/intent/ExecutionIntent.d.ts.map +1 -1
  46. package/dist/intent/ExecutionIntent.js +7 -0
  47. package/dist/intent/ExecutionIntent.js.map +1 -1
  48. package/dist/intent/IntentCapture.d.ts +5 -0
  49. package/dist/intent/IntentCapture.d.ts.map +1 -1
  50. package/dist/intent/IntentCapture.js +12 -1
  51. package/dist/intent/IntentCapture.js.map +1 -1
  52. package/dist/shadow/ShadowPipeline.d.ts +75 -5
  53. package/dist/shadow/ShadowPipeline.d.ts.map +1 -1
  54. package/dist/shadow/ShadowPipeline.js +157 -7
  55. package/dist/shadow/ShadowPipeline.js.map +1 -1
  56. package/dist/testing/Index.d.ts +12 -0
  57. package/dist/testing/Index.d.ts.map +1 -0
  58. package/dist/testing/Index.js +12 -0
  59. package/dist/testing/Index.js.map +1 -0
  60. package/dist/testing/LocalAuthority.d.ts +203 -0
  61. package/dist/testing/LocalAuthority.d.ts.map +1 -0
  62. package/dist/testing/LocalAuthority.js +799 -0
  63. package/dist/testing/LocalAuthority.js.map +1 -0
  64. package/dist/testing/LocalPresence.d.ts +210 -0
  65. package/dist/testing/LocalPresence.d.ts.map +1 -0
  66. package/dist/testing/LocalPresence.js +473 -0
  67. package/dist/testing/LocalPresence.js.map +1 -0
  68. package/package.json +40 -4
@@ -2,26 +2,61 @@ import { z } from "zod";
2
2
  import { AuthorityBaseUrl } from "../http/AuthorityBaseUrl.js";
3
3
  import { BoundedResponseBody } from "../http/BoundedResponseBody.js";
4
4
  import { CanonicalIntentHasher } from "../intent/CanonicalIntentHasher.js";
5
- const ConsumeResponseSchema = z
6
- .object({
5
+ const boundedIdentifier = z
6
+ .string()
7
+ .min(1)
8
+ .max(200)
9
+ .refine(hasNoControlCharacters, { message: "IDENTIFIER_INVALID" });
10
+ const intentHash = z.string().regex(/^sha256:[0-9a-f]{64}$/);
11
+ const CLAIM_TOKEN_PATTERN = /^[\w-]{43,128}$/;
12
+ /**
13
+ * Mirrors `ExecutionTokenResponse` and `ExecutionTokenClaims` in the Decionis
14
+ * OpenAPI contract. Claims are declared `additionalProperties: true`, and the
15
+ * live response carries revalidation fields beyond the documented envelope, so
16
+ * unknown fields are tolerated; authority is established only by the positive
17
+ * assertions checked below, never by an extra field.
18
+ */
19
+ const ClaimResponseSchema = z.looseObject({
7
20
  valid: z.literal(true),
8
- claims: z
9
- .object({
10
- jti: z.string().min(1).max(200),
11
- decision_id: z.string().min(1).max(200),
12
- dossier_id: z.string().min(1).max(200),
21
+ reason_codes: z.array(boundedIdentifier).max(50).optional(),
22
+ should_execute: z.boolean().optional(),
23
+ claims: z.looseObject({
24
+ iss: boundedIdentifier,
25
+ sub: boundedIdentifier.optional(),
26
+ aud: z.string().min(1).max(500).optional(),
27
+ org_id: z.string().uuid(),
28
+ dossier_id: boundedIdentifier,
29
+ decision_id: boundedIdentifier,
30
+ action: boundedIdentifier,
31
+ decision: z.literal("allow"),
32
+ scope: z.literal("execute"),
33
+ binding: z.looseObject({ intent_hash: intentHash }),
34
+ jti: boundedIdentifier,
35
+ iat: z.number().int(),
36
+ nbf: z.number().int(),
13
37
  exp: z.number().int().positive(),
14
- binding: z.object({ intent_hash: z.string().regex(/^sha256:[0-9a-f]{64}$/) }).strict(),
15
- })
16
- .strict(),
17
- })
18
- .strict();
38
+ }),
39
+ claim_token: z.string().regex(CLAIM_TOKEN_PATTERN).nullable().optional(),
40
+ claim_lease_expires_at: z.string().datetime().nullable().optional(),
41
+ });
42
+ const FinalizeResponseSchema = z.looseObject({
43
+ finalized: z.boolean(),
44
+ reason_codes: z.array(boundedIdentifier).max(50).optional(),
45
+ });
19
46
  const MAX_RESPONSE_BYTES = 100 * 1024;
47
+ /**
48
+ * Claims a Decionis execution grant through `/v1/execution/claim-token`
49
+ * immediately before dispatch and finalizes the attempt through
50
+ * `/v1/execution/finalize-token` afterwards, so commit evidence joins the
51
+ * Decision Dossier chain.
52
+ */
20
53
  export class DecionisGrantVerifier {
21
54
  baseUrl;
22
55
  apiKey;
23
56
  timeoutMs;
24
57
  fetchImpl;
58
+ /** Claim material is keyed by the frozen authorization it belongs to and never exposed. */
59
+ claims = new WeakMap();
25
60
  constructor(options) {
26
61
  this.baseUrl = AuthorityBaseUrl.normalize(options.baseUrl, options.allowInsecureLoopback === true);
27
62
  this.apiKey = options.apiKey;
@@ -36,10 +71,11 @@ export class DecionisGrantVerifier {
36
71
  decision.intentHash !== captured.intentHash) {
37
72
  return null;
38
73
  }
74
+ const commitCorrelationId = captured.intent.intentId;
39
75
  const controller = new AbortController();
40
76
  const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
41
77
  try {
42
- const response = await this.fetchImpl(`${this.baseUrl}/v1/execution/consume-token`, {
78
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/execution/claim-token`, {
43
79
  method: "POST",
44
80
  headers: {
45
81
  authorization: `Bearer ${this.apiKey}`,
@@ -49,7 +85,11 @@ export class DecionisGrantVerifier {
49
85
  execution_token: decision.authorization.token,
50
86
  intent_hash: captured.intentHash,
51
87
  intent: CanonicalIntentHasher.bindingOf(captured.intent),
88
+ // A Presence-bound decision is re-verified by the authority at claim
89
+ // time, so the same evidence must accompany the claim.
90
+ ...(decision.evidence === undefined ? {} : { evidence: decision.evidence }),
52
91
  consumed_by: captured.intent.actor.id,
92
+ commit_correlation_id: commitCorrelationId,
53
93
  }),
54
94
  signal: controller.signal,
55
95
  });
@@ -58,24 +98,38 @@ export class DecionisGrantVerifier {
58
98
  const text = await BoundedResponseBody.read(response, MAX_RESPONSE_BYTES);
59
99
  if (text === null)
60
100
  return null;
61
- const parsed = ConsumeResponseSchema.parse(JSON.parse(text));
62
- const claimExpiryMs = parsed.claims.exp * 1_000;
101
+ const parsed = ClaimResponseSchema.parse(JSON.parse(text));
102
+ const claims = parsed.claims;
103
+ const claimExpiryMs = claims.exp * 1_000;
63
104
  const decisionExpirySeconds = Math.floor(Date.parse(decision.authorization.expiresAt) / 1_000);
64
- if (parsed.claims.binding.intent_hash !== captured.intentHash ||
65
- parsed.claims.decision_id !== decision.decisionId ||
66
- parsed.claims.dossier_id !== decision.dossierId ||
67
- parsed.claims.exp !== decisionExpirySeconds ||
105
+ const audience = `${captured.intent.downstreamTarget.system}:${captured.intent.downstreamTarget.operation}`;
106
+ if (parsed.should_execute === false ||
107
+ typeof parsed.claim_token !== "string" ||
108
+ claims.binding.intent_hash !== captured.intentHash ||
109
+ claims.decision_id !== decision.decisionId ||
110
+ claims.dossier_id !== decision.dossierId ||
111
+ claims.org_id !== captured.intent.tenantId ||
112
+ claims.sub !== captured.intent.actor.id ||
113
+ claims.action !== captured.intent.action ||
114
+ (claims.aud !== undefined && claims.aud !== audience) ||
115
+ claims.exp !== decisionExpirySeconds ||
68
116
  claimExpiryMs <= Date.now() ||
69
117
  claimExpiryMs > Date.parse(captured.intent.expiresAt)) {
70
118
  return null;
71
119
  }
72
- return Object.freeze({
73
- decisionId: parsed.claims.decision_id,
74
- dossierId: parsed.claims.dossier_id,
75
- grantId: parsed.claims.jti,
76
- intentHash: parsed.claims.binding.intent_hash,
77
- expiresAt: new Date(parsed.claims.exp * 1_000).toISOString(),
120
+ const authorization = Object.freeze({
121
+ decisionId: claims.decision_id,
122
+ dossierId: claims.dossier_id,
123
+ grantId: claims.jti,
124
+ intentHash: claims.binding.intent_hash,
125
+ expiresAt: new Date(claims.exp * 1_000).toISOString(),
78
126
  });
127
+ this.claims.set(authorization, {
128
+ executionToken: decision.authorization.token,
129
+ claimToken: parsed.claim_token,
130
+ commitCorrelationId,
131
+ });
132
+ return authorization;
79
133
  }
80
134
  catch {
81
135
  return null;
@@ -84,5 +138,52 @@ export class DecionisGrantVerifier {
84
138
  clearTimeout(timeout);
85
139
  }
86
140
  }
141
+ async finalize(input) {
142
+ const claim = this.claims.get(input.authorization);
143
+ if (claim === undefined)
144
+ return "PENDING";
145
+ const controller = new AbortController();
146
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
147
+ try {
148
+ const response = await this.fetchImpl(`${this.baseUrl}/v1/execution/finalize-token`, {
149
+ method: "POST",
150
+ headers: {
151
+ authorization: `Bearer ${this.apiKey}`,
152
+ "content-type": "application/json",
153
+ },
154
+ body: JSON.stringify({
155
+ execution_token: claim.executionToken,
156
+ claim_token: claim.claimToken,
157
+ outcome: input.outcome,
158
+ commit_correlation_id: claim.commitCorrelationId,
159
+ }),
160
+ signal: controller.signal,
161
+ });
162
+ if (!response.ok)
163
+ return "PENDING";
164
+ const text = await BoundedResponseBody.read(response, MAX_RESPONSE_BYTES);
165
+ if (text === null)
166
+ return "PENDING";
167
+ const parsed = FinalizeResponseSchema.parse(JSON.parse(text));
168
+ if (!parsed.finalized)
169
+ return "PENDING";
170
+ this.claims.delete(input.authorization);
171
+ return "RECORDED";
172
+ }
173
+ catch {
174
+ return "PENDING";
175
+ }
176
+ finally {
177
+ clearTimeout(timeout);
178
+ }
179
+ }
180
+ }
181
+ function hasNoControlCharacters(value) {
182
+ for (let index = 0; index < value.length; index += 1) {
183
+ const codeUnit = value.charCodeAt(index);
184
+ if (codeUnit <= 0x1f || codeUnit === 0x7f)
185
+ return false;
186
+ }
187
+ return true;
87
188
  }
88
189
  //# sourceMappingURL=AuthorizationVerifier.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuthorizationVerifier.js","sourceRoot":"","sources":["../../src/execution/AuthorizationVerifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AuthorizationVerifier.js","sourceRoot":"","sources":["../../src/execution/AuthorizationVerifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AA0C3E,MAAM,iBAAiB,GAAG,CAAC;KACxB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,MAAM,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;AACrE,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;AAC7D,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,CAAC,CAAC,WAAW,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACtB,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3D,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACtC,MAAM,EAAE,CAAC,CAAC,WAAW,CAAC;QACpB,GAAG,EAAE,iBAAiB;QACtB,GAAG,EAAE,iBAAiB,CAAC,QAAQ,EAAE;QACjC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC1C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;QACzB,UAAU,EAAE,iBAAiB;QAC7B,WAAW,EAAE,iBAAiB;QAC9B,MAAM,EAAE,iBAAiB;QACzB,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QAC5B,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAC3B,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;QACnD,GAAG,EAAE,iBAAiB;QACtB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;QACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;QACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;KACjC,CAAC;IACF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxE,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACpE,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,CAAC,CAAC,WAAW,CAAC;IAC3C,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;IACtB,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC5D,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,GAAG,GAAG,IAAI,CAAC;AAgBtC;;;;;GAKG;AACH,MAAM,OAAO,qBAAqB;IACf,OAAO,CAAS;IAChB,MAAM,CAAS;IACf,SAAS,CAAS;IAClB,SAAS,CAAe;IACzC,2FAA2F;IAC1E,MAAM,GAAG,IAAI,OAAO,EAAsC,CAAC;IAE5E,YAAmB,OAAqC;QACtD,IAAI,CAAC,OAAO,GAAG,gBAAgB,CAAC,SAAS,CACvC,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,qBAAqB,KAAK,IAAI,CACvC,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3E,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IAC1C,CAAC;IAEM,KAAK,CAAC,gBAAgB,CAC3B,QAAwB,EACxB,QAAsB;QAEtB,IACE,QAAQ,CAAC,OAAO,KAAK,OAAO;YAC5B,QAAQ,CAAC,UAAU;YACnB,QAAQ,CAAC,aAAa,KAAK,IAAI;YAC/B,QAAQ,CAAC,SAAS,KAAK,IAAI;YAC3B,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU,EAC3C,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC;QACrD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,2BAA2B,EAAE;gBAChF,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;oBACtC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,eAAe,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK;oBAC7C,WAAW,EAAE,QAAQ,CAAC,UAAU;oBAChC,MAAM,EAAE,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACxD,qEAAqE;oBACrE,uDAAuD;oBACvD,GAAG,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;oBAC3E,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;oBACrC,qBAAqB,EAAE,mBAAmB;iBAC3C,CAAC;gBACF,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC9B,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;YAC1E,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC7B,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC;YACzC,MAAM,qBAAqB,GAAG,IAAI,CAAC,KAAK,CACtC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,KAAK,CACrD,CAAC;YACF,MAAM,QAAQ,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC;YAC5G,IACE,MAAM,CAAC,cAAc,KAAK,KAAK;gBAC/B,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;gBACtC,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,UAAU;gBAClD,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC,UAAU;gBAC1C,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,SAAS;gBACxC,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC,QAAQ;gBAC1C,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;gBACvC,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM;gBACxC,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC;gBACrD,MAAM,CAAC,GAAG,KAAK,qBAAqB;gBACpC,aAAa,IAAI,IAAI,CAAC,GAAG,EAAE;gBAC3B,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,EACrD,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,aAAa,GAA0B,MAAM,CAAC,MAAM,CAAC;gBACzD,UAAU,EAAE,MAAM,CAAC,WAAW;gBAC9B,SAAS,EAAE,MAAM,CAAC,UAAU;gBAC5B,OAAO,EAAE,MAAM,CAAC,GAAG;gBACnB,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW;gBACtC,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE;aACtD,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE;gBAC7B,cAAc,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK;gBAC5C,UAAU,EAAE,MAAM,CAAC,WAAW;gBAC9B,mBAAmB;aACpB,CAAC,CAAC;YACH,OAAO,aAAa,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,KAAqC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC1C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,8BAA8B,EAAE;gBACnF,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;oBACtC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,eAAe,EAAE,KAAK,CAAC,cAAc;oBACrC,WAAW,EAAE,KAAK,CAAC,UAAU;oBAC7B,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,qBAAqB,EAAE,KAAK,CAAC,mBAAmB;iBACjD,CAAC;gBACF,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,OAAO,SAAS,CAAC;YACnC,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;YAC1E,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,SAAS,CAAC;YACpC,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9D,IAAI,CAAC,MAAM,CAAC,SAAS;gBAAE,OAAO,SAAS,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YACxC,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,25 +1,90 @@
1
1
  import type { GateDecision } from "../decision/DecisionAuthority.js";
2
+ import type { AuditRecorder } from "../audit/AuditRecorder.js";
2
3
  import type { CapturedIntent } from "../intent/ExecutionIntent.js";
3
4
  import type { ActionRegistry } from "./ActionRegistry.js";
4
- import type { AuthorizationVerifier, VerifiedAuthorization } from "./AuthorizationVerifier.js";
5
- export type ExecutionBlockReason = "DECISION_NOT_ALLOW" | "INTENT_BINDING_MISMATCH" | "INTENT_CONFORMANCE_FAILED" | "AUTHORIZATION_MISSING" | "AUTHORIZATION_INVALID";
5
+ import type { AuthorizationVerifier, ExecutionFinalization, VerifiedAuthorization } from "./AuthorizationVerifier.js";
6
+ export type ExecutionBlockReason = "DECISION_NOT_AUTHORITATIVE" | "DECISION_NOT_ALLOW" | "INTENT_BINDING_MISMATCH" | "INTENT_CONFORMANCE_FAILED" | "AUTHORIZATION_MISSING" | "AUTHORIZATION_INVALID" | "RECOVERY_BINDING_MISMATCH" | "AUDIT_UNAVAILABLE";
7
+ export type ExecutionPreDispatchFailureReason = "HANDLER_FAILED_BEFORE_DISPATCH" | "AUDIT_UNAVAILABLE";
8
+ export interface ExecutionRecoveryReference extends VerifiedAuthorization {
9
+ readonly version: "agent-safe.recovery/1";
10
+ readonly idempotencyKey: string;
11
+ }
12
+ export interface ExecutionBlockedResult {
13
+ readonly outcome: "BLOCKED";
14
+ readonly executed: false;
15
+ readonly reason: ExecutionBlockReason;
16
+ readonly result: null;
17
+ readonly authorization: null;
18
+ }
19
+ export interface ProviderOutcomeUnknownResult {
20
+ readonly outcome: "UNKNOWN_AFTER_DISPATCH";
21
+ readonly executed: null;
22
+ readonly reason: "PROVIDER_OUTCOME_UNKNOWN";
23
+ readonly result: null;
24
+ readonly authorization: VerifiedAuthorization;
25
+ readonly recovery: ExecutionRecoveryReference;
26
+ }
27
+ /**
28
+ * Every outcome that consumed a grant reports whether the attempt's commit
29
+ * evidence reached the authority. Finalization is evidence, not authority;
30
+ * it never changes `outcome` or `executed`.
31
+ */
6
32
  export type SafeExecutionResult<TResult = unknown> = {
33
+ readonly outcome: "COMPLETED";
7
34
  readonly executed: true;
35
+ readonly recovered: false;
36
+ readonly result: TResult;
37
+ readonly authorization: VerifiedAuthorization;
38
+ readonly finalization: ExecutionFinalization;
39
+ } | ExecutionBlockedResult | {
40
+ readonly outcome: "FAILED_BEFORE_DISPATCH";
41
+ readonly executed: false;
42
+ readonly reason: ExecutionPreDispatchFailureReason;
43
+ readonly result: null;
44
+ readonly authorization: VerifiedAuthorization;
45
+ readonly finalization: ExecutionFinalization;
46
+ } | (ProviderOutcomeUnknownResult & {
47
+ readonly finalization: ExecutionFinalization;
48
+ });
49
+ export type ExecutionReconciliationResult<TResult = unknown> = {
50
+ readonly outcome: "COMPLETED";
51
+ readonly executed: true;
52
+ readonly recovered: true;
8
53
  readonly result: TResult;
9
54
  readonly authorization: VerifiedAuthorization;
10
55
  } | {
56
+ readonly outcome: "DEFINITELY_NOT_EXECUTED";
11
57
  readonly executed: false;
12
- readonly reason: ExecutionBlockReason;
58
+ readonly recovered: true;
59
+ readonly reason: "PROVIDER_CONFIRMED_NOT_EXECUTED";
13
60
  readonly result: null;
14
- readonly authorization: null;
15
- };
61
+ readonly authorization: VerifiedAuthorization;
62
+ } | ExecutionBlockedResult | ProviderOutcomeUnknownResult;
16
63
  export declare class SafeExecutor {
17
64
  private readonly registry;
18
65
  private readonly verifier;
66
+ private readonly audit?;
19
67
  private readonly hasher;
20
- constructor(registry: ActionRegistry, verifier: AuthorizationVerifier);
68
+ constructor(registry: ActionRegistry, verifier: AuthorizationVerifier, audit?: AuditRecorder | undefined);
21
69
  run<TResult = unknown>(captured: CapturedIntent, decision: GateDecision): Promise<SafeExecutionResult<TResult>>;
70
+ /**
71
+ * Reports the attempt outcome to the verifier's authority. A missing,
72
+ * failing, or malformed finalization can never alter the execution result.
73
+ */
74
+ private finalize;
75
+ private static finalizationCode;
76
+ reconcile<TResult = unknown>(captured: CapturedIntent, recovery: ExecutionRecoveryReference): Promise<ExecutionReconciliationResult<TResult>>;
77
+ /**
78
+ * Observational artifacts carry an explicit non-authoritative marker. They
79
+ * must never be interpreted as a `GateDecision`, even after an unsafe cast
80
+ * or a JSON round trip.
81
+ */
82
+ private static isAuthoritativeDecision;
22
83
  private intentConforms;
23
- private static blocked;
84
+ private block;
85
+ private failedBeforeDispatch;
86
+ private record;
87
+ private static recoveryReference;
88
+ private static authorizationFrom;
24
89
  }
25
90
  //# sourceMappingURL=SafeExecutor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SafeExecutor.d.ts","sourceRoot":"","sources":["../../src/execution/SafeExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAE/F,MAAM,MAAM,oBAAoB,GAC5B,oBAAoB,GACpB,yBAAyB,GACzB,2BAA2B,GAC3B,uBAAuB,GACvB,uBAAuB,CAAC;AAE5B,MAAM,MAAM,mBAAmB,CAAC,OAAO,GAAG,OAAO,IAC7C;IACE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;CAC/C,GACD;IACE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;CAC9B,CAAC;AAEN,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJ3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;gBAGnC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,qBAAqB;IAGrC,GAAG,CAAC,OAAO,GAAG,OAAO,EAChC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IA8CxC,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,MAAM,CAAC,OAAO;CAGvB"}
1
+ {"version":3,"file":"SafeExecutor.d.ts","sourceRoot":"","sources":["../../src/execution/SafeExecutor.ts"],"names":[],"mappings":"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"}
@@ -2,23 +2,44 @@ import { CanonicalIntentHasher } from "../intent/CanonicalIntentHasher.js";
2
2
  export class SafeExecutor {
3
3
  registry;
4
4
  verifier;
5
+ audit;
5
6
  hasher = new CanonicalIntentHasher();
6
- constructor(registry, verifier) {
7
+ constructor(registry, verifier, audit) {
7
8
  this.registry = registry;
8
9
  this.verifier = verifier;
10
+ this.audit = audit;
9
11
  }
10
12
  async run(captured, decision) {
13
+ const startedAt = Date.now();
14
+ // Stryker disable next-line all: Audit payload mapping is covered by lifecycle event assertions.
15
+ const intentRecorded = await this.record({ eventType: "INTENT_CAPTURED", captured });
16
+ // An observational artifact (shadow observation, serialized audit event) is
17
+ // rejected before it is recorded or inspected as if it were a decision.
18
+ if (!SafeExecutor.isAuthoritativeDecision(decision)) {
19
+ return await this.block(captured, undefined, "DECISION_NOT_AUTHORITATIVE", startedAt);
20
+ }
21
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
22
+ const decisionRecorded = await this.record({
23
+ eventType: decision.failClosed ? "AUTHORITY_FAILED_CLOSED" : "AUTHORITY_DECISION",
24
+ captured,
25
+ decision,
26
+ });
27
+ // Stryker restore all
28
+ if (this.audit?.requiresDeliveryBeforeExecution === true &&
29
+ (!intentRecorded || !decisionRecorded)) {
30
+ return await this.block(captured, decision, "AUDIT_UNAVAILABLE", startedAt);
31
+ }
11
32
  if (decision.verdict !== "ALLOW" || decision.failClosed) {
12
- return SafeExecutor.blocked("DECISION_NOT_ALLOW");
33
+ return await this.block(captured, decision, "DECISION_NOT_ALLOW", startedAt);
13
34
  }
14
35
  if (decision.intentHash !== captured.intentHash) {
15
- return SafeExecutor.blocked("INTENT_BINDING_MISMATCH");
36
+ return await this.block(captured, decision, "INTENT_BINDING_MISMATCH", startedAt);
16
37
  }
17
- if (decision.authorization === null) {
18
- return SafeExecutor.blocked("AUTHORIZATION_MISSING");
38
+ if (decision.authorization === null || typeof decision.authorization !== "object") {
39
+ return await this.block(captured, decision, "AUTHORIZATION_MISSING", startedAt);
19
40
  }
20
41
  if (!this.intentConforms(captured)) {
21
- return SafeExecutor.blocked("INTENT_CONFORMANCE_FAILED");
42
+ return await this.block(captured, decision, "INTENT_CONFORMANCE_FAILED", startedAt);
22
43
  }
23
44
  this.registry.validate(captured);
24
45
  let authorization;
@@ -26,10 +47,11 @@ export class SafeExecutor {
26
47
  authorization = await this.verifier.verifyAndConsume(captured, decision);
27
48
  }
28
49
  catch {
29
- return SafeExecutor.blocked("AUTHORIZATION_INVALID");
50
+ return await this.block(captured, decision, "AUTHORIZATION_INVALID", startedAt);
51
+ }
52
+ if (authorization === null) {
53
+ return await this.block(captured, decision, "AUTHORIZATION_INVALID", startedAt);
30
54
  }
31
- if (authorization === null)
32
- return SafeExecutor.blocked("AUTHORIZATION_INVALID");
33
55
  const authorizationExpiry = Date.parse(authorization.expiresAt);
34
56
  if (authorization.decisionId !== decision.decisionId ||
35
57
  authorization.dossierId !== decision.dossierId ||
@@ -39,19 +61,182 @@ export class SafeExecutor {
39
61
  Math.floor(Date.parse(decision.authorization.expiresAt) / 1_000) ||
40
62
  authorizationExpiry <= Date.now() ||
41
63
  authorizationExpiry > Date.parse(captured.intent.expiresAt)) {
42
- return SafeExecutor.blocked("AUTHORIZATION_INVALID");
64
+ return await this.block(captured, decision, "AUTHORIZATION_INVALID", startedAt);
43
65
  }
44
66
  if (!this.intentConforms(captured)) {
45
- return SafeExecutor.blocked("INTENT_CONFORMANCE_FAILED");
67
+ return await this.block(captured, decision, "INTENT_CONFORMANCE_FAILED", startedAt);
68
+ }
69
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
70
+ const grantRecorded = await this.record({
71
+ eventType: "GRANT_CONSUMED",
72
+ captured,
73
+ decision,
74
+ authorization,
75
+ durationMs: Date.now() - startedAt,
76
+ });
77
+ // Stryker restore all
78
+ if (this.audit?.requiresDeliveryBeforeExecution === true && !grantRecorded) {
79
+ return await this.failedBeforeDispatch(captured, decision, authorization, "AUDIT_UNAVAILABLE", startedAt);
80
+ }
81
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
82
+ const executionRecorded = await this.record({
83
+ eventType: "EXECUTION_STARTED",
84
+ captured,
85
+ decision,
86
+ authorization,
87
+ durationMs: Date.now() - startedAt,
88
+ });
89
+ // Stryker restore all
90
+ if (this.audit?.requiresDeliveryBeforeExecution === true && !executionRecorded) {
91
+ return await this.failedBeforeDispatch(captured, decision, authorization, "AUDIT_UNAVAILABLE", startedAt);
46
92
  }
47
- let result;
93
+ const attempt = await this.registry.executeTracked(captured, authorization);
94
+ if (attempt.status === "FAILED_BEFORE_DISPATCH") {
95
+ return await this.failedBeforeDispatch(captured, decision, authorization, "HANDLER_FAILED_BEFORE_DISPATCH", startedAt);
96
+ }
97
+ if (attempt.status === "UNKNOWN_AFTER_DISPATCH") {
98
+ const finalization = await this.finalize(captured, decision, authorization, "INDETERMINATE");
99
+ const result = {
100
+ outcome: "UNKNOWN_AFTER_DISPATCH",
101
+ executed: null,
102
+ reason: "PROVIDER_OUTCOME_UNKNOWN",
103
+ result: null,
104
+ authorization,
105
+ recovery: SafeExecutor.recoveryReference(captured, authorization),
106
+ finalization,
107
+ };
108
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
109
+ await this.record({
110
+ eventType: "EXECUTION_OUTCOME_UNKNOWN",
111
+ captured,
112
+ decision,
113
+ authorization,
114
+ reasonCodes: [result.reason, SafeExecutor.finalizationCode(finalization)],
115
+ durationMs: Date.now() - startedAt,
116
+ });
117
+ // Stryker restore all
118
+ return result;
119
+ }
120
+ const finalization = await this.finalize(captured, decision, authorization, "COMMITTED");
121
+ const result = {
122
+ outcome: "COMPLETED",
123
+ executed: true,
124
+ recovered: false,
125
+ result: attempt.result,
126
+ authorization,
127
+ finalization,
128
+ };
129
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
130
+ await this.record({
131
+ eventType: "EXECUTION_COMPLETED",
132
+ captured,
133
+ decision,
134
+ authorization,
135
+ reasonCodes: [SafeExecutor.finalizationCode(finalization)],
136
+ durationMs: Date.now() - startedAt,
137
+ });
138
+ // Stryker restore all
139
+ return result;
140
+ }
141
+ /**
142
+ * Reports the attempt outcome to the verifier's authority. A missing,
143
+ * failing, or malformed finalization can never alter the execution result.
144
+ */
145
+ async finalize(captured, decision, authorization, outcome) {
146
+ const finalize = this.verifier.finalize;
147
+ if (finalize === undefined)
148
+ return "UNSUPPORTED";
48
149
  try {
49
- result = (await this.registry.execute(captured, authorization));
150
+ const status = await finalize.call(this.verifier, {
151
+ captured,
152
+ decision,
153
+ authorization,
154
+ outcome,
155
+ });
156
+ return status === "RECORDED" ? "RECORDED" : "PENDING";
50
157
  }
51
158
  catch {
52
- throw new Error("ACTION_EXECUTION_FAILED");
159
+ return "PENDING";
160
+ }
161
+ }
162
+ static finalizationCode(finalization) {
163
+ return `COMMIT_FINALIZATION_${finalization}`;
164
+ }
165
+ async reconcile(captured, recovery) {
166
+ const startedAt = Date.now();
167
+ if (recovery.version !== "agent-safe.recovery/1" ||
168
+ recovery.intentHash !== captured.intentHash ||
169
+ recovery.idempotencyKey !== captured.intent.idempotencyKey ||
170
+ !this.intentConforms(captured)) {
171
+ return await this.block(captured, undefined, "RECOVERY_BINDING_MISMATCH", startedAt);
172
+ }
173
+ const reconciliation = await this.registry.reconcile(captured, recovery.idempotencyKey);
174
+ const authorization = SafeExecutor.authorizationFrom(recovery);
175
+ if (reconciliation.status === "COMPLETED") {
176
+ const result = {
177
+ outcome: "COMPLETED",
178
+ executed: true,
179
+ recovered: true,
180
+ result: reconciliation.result,
181
+ authorization,
182
+ };
183
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
184
+ await this.record({
185
+ eventType: "RECONCILIATION_COMPLETED",
186
+ captured,
187
+ authorization,
188
+ durationMs: Date.now() - startedAt,
189
+ });
190
+ // Stryker restore all
191
+ return result;
53
192
  }
54
- return { executed: true, result, authorization };
193
+ if (reconciliation.status === "NOT_EXECUTED") {
194
+ const result = {
195
+ outcome: "DEFINITELY_NOT_EXECUTED",
196
+ executed: false,
197
+ recovered: true,
198
+ reason: "PROVIDER_CONFIRMED_NOT_EXECUTED",
199
+ result: null,
200
+ authorization,
201
+ };
202
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
203
+ await this.record({
204
+ eventType: "RECONCILIATION_NOT_EXECUTED",
205
+ captured,
206
+ authorization,
207
+ reasonCodes: [result.reason],
208
+ durationMs: Date.now() - startedAt,
209
+ });
210
+ // Stryker restore all
211
+ return result;
212
+ }
213
+ const result = {
214
+ outcome: "UNKNOWN_AFTER_DISPATCH",
215
+ executed: null,
216
+ reason: "PROVIDER_OUTCOME_UNKNOWN",
217
+ result: null,
218
+ authorization,
219
+ recovery,
220
+ };
221
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
222
+ await this.record({
223
+ eventType: "RECONCILIATION_UNKNOWN",
224
+ captured,
225
+ authorization,
226
+ reasonCodes: [result.reason],
227
+ durationMs: Date.now() - startedAt,
228
+ });
229
+ // Stryker restore all
230
+ return result;
231
+ }
232
+ /**
233
+ * Observational artifacts carry an explicit non-authoritative marker. They
234
+ * must never be interpreted as a `GateDecision`, even after an unsafe cast
235
+ * or a JSON round trip.
236
+ */
237
+ static isAuthoritativeDecision(decision) {
238
+ const candidate = decision;
239
+ return candidate.authority !== "OBSERVATIONAL" && candidate.mode !== "SHADOW";
55
240
  }
56
241
  intentConforms(captured) {
57
242
  let recomputed = null;
@@ -66,8 +251,59 @@ export class SafeExecutor {
66
251
  recomputed.canonicalIntent === captured.canonicalIntent &&
67
252
  recomputed.byteLength === captured.byteLength);
68
253
  }
69
- static blocked(reason) {
70
- return { executed: false, reason, result: null, authorization: null };
254
+ async block(captured, decision, reason, startedAt) {
255
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
256
+ await this.record({
257
+ eventType: "EXECUTION_BLOCKED",
258
+ captured,
259
+ ...(decision === undefined ? {} : { decision }),
260
+ reasonCodes: [reason],
261
+ durationMs: Date.now() - startedAt,
262
+ });
263
+ // Stryker restore all
264
+ return { outcome: "BLOCKED", executed: false, reason, result: null, authorization: null };
265
+ }
266
+ async failedBeforeDispatch(captured, decision, authorization, reason, startedAt) {
267
+ // The grant was consumed but no side effect was attempted.
268
+ const finalization = await this.finalize(captured, decision, authorization, "FAILED");
269
+ // Stryker disable all: Audit payload mapping is covered by lifecycle event assertions.
270
+ await this.record({
271
+ eventType: "EXECUTION_FAILED_BEFORE_DISPATCH",
272
+ captured,
273
+ decision,
274
+ authorization,
275
+ reasonCodes: [reason, SafeExecutor.finalizationCode(finalization)],
276
+ durationMs: Date.now() - startedAt,
277
+ });
278
+ // Stryker restore all
279
+ return {
280
+ outcome: "FAILED_BEFORE_DISPATCH",
281
+ executed: false,
282
+ reason,
283
+ result: null,
284
+ authorization,
285
+ finalization,
286
+ };
287
+ }
288
+ async record(input) {
289
+ // Stryker disable next-line BooleanLiteral: The value is irrelevant when no audit policy exists.
290
+ return this.audit === undefined ? true : await this.audit.record(input);
291
+ }
292
+ static recoveryReference(captured, authorization) {
293
+ return Object.freeze({
294
+ version: "agent-safe.recovery/1",
295
+ ...authorization,
296
+ idempotencyKey: captured.intent.idempotencyKey,
297
+ });
298
+ }
299
+ static authorizationFrom(recovery) {
300
+ return Object.freeze({
301
+ decisionId: recovery.decisionId,
302
+ dossierId: recovery.dossierId,
303
+ grantId: recovery.grantId,
304
+ intentHash: recovery.intentHash,
305
+ expiresAt: recovery.expiresAt,
306
+ });
71
307
  }
72
308
  }
73
309
  //# sourceMappingURL=SafeExecutor.js.map