@ixo/editor 5.39.0 → 6.0.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.
@@ -1,5 +1,20 @@
1
+ // src/core/lib/warnOnce.ts
2
+ var seen = /* @__PURE__ */ new Set();
3
+ function warnOnce(key, message) {
4
+ if (seen.has(key)) return;
5
+ seen.add(key);
6
+ console.warn(message);
7
+ }
8
+
1
9
  // src/core/lib/actionRegistry/registry.ts
2
10
  var actions = /* @__PURE__ */ new Map();
11
+ var STEP_COMPLETED_EVENT_NAME = "step.completed";
12
+ var STEP_COMPLETED_EVENT = {
13
+ name: STEP_COMPLETED_EVENT_NAME,
14
+ displayName: "Completed",
15
+ description: "Fires when this step is completed.",
16
+ payloadSchema: []
17
+ };
3
18
  var ACTION_TYPE_ALIASES = {
4
19
  bid: "qi/bid.submit",
5
20
  claim: "qi/claim.submit",
@@ -40,6 +55,13 @@ function getAction(type) {
40
55
  function getAllActions() {
41
56
  return Array.from(actions.values());
42
57
  }
58
+ function isRepeatableAction(type) {
59
+ if (!type) return false;
60
+ return getAction(type)?.cardinality === "many";
61
+ }
62
+ function getAliasEntries() {
63
+ return Array.from(aliases.entries());
64
+ }
43
65
  function hasAction(type) {
44
66
  return actions.has(resolveActionType(type));
45
67
  }
@@ -51,28 +73,49 @@ function getActionByCan(can) {
51
73
  }
52
74
  function getEventsForBlock(action, inputs) {
53
75
  if (!action) return [];
76
+ let events;
54
77
  if (action.getDynamicEvents) {
55
78
  const parsed = normalizeInputs(inputs);
56
79
  try {
57
- return action.getDynamicEvents(parsed) || [];
58
- } catch {
59
- return [];
80
+ const dynamic = action.getDynamicEvents(parsed) || [];
81
+ events = action.dynamicResolutionMode === "replace" ? dynamic : mergeByKey(action.events || [], dynamic, (event) => event.name);
82
+ } catch (error) {
83
+ warnOnce(
84
+ `dynamic-events:${action.type}`,
85
+ `[flow-config] action ${action.type}: getDynamicEvents threw (${error instanceof Error ? error.message : String(error)}); no events resolved`
86
+ );
87
+ events = [];
60
88
  }
89
+ } else {
90
+ events = action.events || [];
91
+ }
92
+ if (events.some((event) => event.name === STEP_COMPLETED_EVENT_NAME)) {
93
+ return events;
61
94
  }
62
- return action.events || [];
95
+ return [...events, STEP_COMPLETED_EVENT];
63
96
  }
64
97
  function getOutputSchemaForBlock(action, inputs) {
65
98
  if (!action) return [];
66
99
  if (action.getDynamicOutputSchema) {
67
100
  const parsed = normalizeInputs(inputs);
68
101
  try {
69
- return action.getDynamicOutputSchema(parsed) || [];
70
- } catch {
102
+ const dynamic = action.getDynamicOutputSchema(parsed) || [];
103
+ if (action.dynamicResolutionMode === "replace") return dynamic;
104
+ return mergeByKey(action.outputSchema || [], dynamic, (field) => field.path);
105
+ } catch (error) {
106
+ warnOnce(
107
+ `dynamic-output-schema:${action.type}`,
108
+ `[flow-config] action ${action.type}: getDynamicOutputSchema threw (${error instanceof Error ? error.message : String(error)}); using static schema`
109
+ );
71
110
  return action.outputSchema || [];
72
111
  }
73
112
  }
74
113
  return action.outputSchema || [];
75
114
  }
115
+ function mergeByKey(baseline, dynamic, keyOf) {
116
+ const dynamicKeys = new Set(dynamic.map(keyOf));
117
+ return [...baseline.filter((item) => !dynamicKeys.has(keyOf(item))), ...dynamic];
118
+ }
76
119
  function normalizeInputs(inputs) {
77
120
  if (!inputs) return {};
78
121
  if (typeof inputs === "object") return inputs;
@@ -81,12 +124,56 @@ function normalizeInputs(inputs) {
81
124
  const parsed = JSON.parse(inputs || "{}");
82
125
  return parsed && typeof parsed === "object" ? parsed : {};
83
126
  } catch {
127
+ warnOnce(`inputs-normalize:${inputs}`, `[flow-config] block inputs are not valid JSON; treated as empty`);
84
128
  return {};
85
129
  }
86
130
  }
87
131
  return {};
88
132
  }
89
133
 
134
+ // src/core/lib/actionRegistry/manifest.ts
135
+ function serializeProof(action) {
136
+ const proof = action.proof;
137
+ if (proof === "none" || proof === void 0) return { kind: "none" };
138
+ if ("validate" in proof) return { kind: "custom" };
139
+ return { kind: "fields", fields: [...proof.fields] };
140
+ }
141
+ function generateActionManifest() {
142
+ const aliasEntries = getAliasEntries();
143
+ const aliasesByType = /* @__PURE__ */ new Map();
144
+ for (const [alias, canonical] of aliasEntries) {
145
+ const list = aliasesByType.get(canonical) || [];
146
+ list.push(alias);
147
+ aliasesByType.set(canonical, list);
148
+ }
149
+ const actions2 = getAllActions().map((action) => {
150
+ const entry = {
151
+ type: action.type,
152
+ aliases: (aliasesByType.get(action.type) || []).sort(),
153
+ sideEffect: action.sideEffect,
154
+ defaultRequiresConfirmation: action.defaultRequiresConfirmation,
155
+ proof: serializeProof(action),
156
+ hasDynamicEvents: !!action.getDynamicEvents,
157
+ hasDynamicOutputSchema: !!action.getDynamicOutputSchema,
158
+ eligibleForEventTrigger: !!action.eligibleForEventTrigger
159
+ };
160
+ if (action.can) entry.can = action.can;
161
+ if (action.requiredCapability) entry.requiredCapability = action.requiredCapability;
162
+ if (action.inputSchema) entry.inputSchema = action.inputSchema;
163
+ if (action.outputSchema) entry.outputSchema = action.outputSchema;
164
+ if (action.events) {
165
+ entry.events = action.events.map((event) => ({
166
+ name: event.name,
167
+ displayName: event.displayName,
168
+ description: event.description,
169
+ payloadSchema: event.payloadSchema
170
+ }));
171
+ }
172
+ return entry;
173
+ }).sort((a, b) => a.type.localeCompare(b.type));
174
+ return { manifestVersion: "1", actions: actions2 };
175
+ }
176
+
90
177
  // src/core/lib/actionRegistry/canMapping.ts
91
178
  var CAN_TO_TYPE = {
92
179
  "bid/submit": "qi/bid.submit",
@@ -154,7 +241,37 @@ function getAllCanMappings() {
154
241
  }
155
242
 
156
243
  // src/core/lib/actionRegistry/adapters.ts
244
+ var SERVICE_GROUP_REQUIRED_HANDLERS = {
245
+ bid: ["submitBid", "approveBid", "rejectBid", "approveServiceAgentApplication", "approveEvaluatorApplication"],
246
+ claim: ["requestPin", "submitClaim", "evaluateClaim", "getCurrentUser"],
247
+ collection: [
248
+ "collection.get",
249
+ "collection.create",
250
+ "collection.updateState",
251
+ "collection.updateDates",
252
+ "collection.updateQuota",
253
+ "collection.updatePayments",
254
+ "collection.updateIntents"
255
+ ],
256
+ collectionUsers: ["collectionUsers.grant", "collectionUsers.revoke", "collectionUsers.list", "collectionUsers.classifyAddress", "collectionUsers.enumerateMembers"],
257
+ carbon: ["carbon.loadBatches", "carbon.harvest", "carbon.retire"]
258
+ };
259
+ function getHandlerAtPath(handlers, path) {
260
+ return path.split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], handlers);
261
+ }
262
+ function warnPartialServiceGroups(handlers) {
263
+ if (!handlers) return;
264
+ for (const [group, keys] of Object.entries(SERVICE_GROUP_REQUIRED_HANDLERS)) {
265
+ const missing = keys.filter((key) => !getHandlerAtPath(handlers, key));
266
+ if (missing.length > 0 && missing.length < keys.length) {
267
+ console.warn(
268
+ `[buildServicesFromHandlers] services.${group} is DISABLED: handlers provide ${keys.length - missing.length}/${keys.length} required methods; missing: ${missing.join(", ")}`
269
+ );
270
+ }
271
+ }
272
+ }
157
273
  function buildServicesFromHandlers(handlers) {
274
+ warnPartialServiceGroups(handlers);
158
275
  return {
159
276
  http: {
160
277
  request: async (params) => {
@@ -362,6 +479,7 @@ registerAction({
362
479
  type: "qi/pod.domain-indexer-lookup",
363
480
  can: "pod/domain-indexer-lookup",
364
481
  sideEffect: false,
482
+ proof: "none",
365
483
  defaultRequiresConfirmation: false,
366
484
  inputSchema: {
367
485
  type: "object",
@@ -401,6 +519,7 @@ registerAction({
401
519
  type: "qi/pod.domain-single-selection",
402
520
  can: "pod/domain-single-selection",
403
521
  sideEffect: false,
522
+ proof: "none",
404
523
  defaultRequiresConfirmation: false,
405
524
  inputSchema: {
406
525
  type: "object",
@@ -455,6 +574,7 @@ registerAction({
455
574
  type: "qi/pod.entity-single-selection",
456
575
  can: "pod/entity-single-selection",
457
576
  sideEffect: false,
577
+ proof: "none",
458
578
  defaultRequiresConfirmation: false,
459
579
  inputSchema: {
460
580
  type: "object",
@@ -513,6 +633,7 @@ registerAction({
513
633
  type: "qi/pod.member-multi-select",
514
634
  can: "pod/member-multi-select",
515
635
  sideEffect: false,
636
+ proof: "none",
516
637
  defaultRequiresConfirmation: false,
517
638
  inputSchema: {
518
639
  type: "object",
@@ -606,6 +727,7 @@ registerAction({
606
727
  type: "qi/pod.governance-config",
607
728
  can: "pod/governance-config",
608
729
  sideEffect: false,
730
+ proof: "none",
609
731
  defaultRequiresConfirmation: false,
610
732
  inputSchema: {
611
733
  type: "object",
@@ -690,6 +812,7 @@ registerAction({
690
812
  type: "qi/pod.list-domain-flows",
691
813
  can: "pod/list-domain-flows",
692
814
  sideEffect: false,
815
+ proof: "none",
693
816
  defaultRequiresConfirmation: false,
694
817
  inputSchema: {
695
818
  type: "object",
@@ -779,6 +902,7 @@ registerAction({
779
902
  type: "qi/governance.member-proposal",
780
903
  can: "governance/member-proposal",
781
904
  sideEffect: true,
905
+ proof: { fields: ["proposalId"] },
782
906
  defaultRequiresConfirmation: true,
783
907
  requiredCapability: "flow/block/execute",
784
908
  outputSchema: [
@@ -794,6 +918,9 @@ registerAction({
794
918
  if (!handlers) {
795
919
  throw new Error("Handlers not available");
796
920
  }
921
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
922
+ throw new Error("Governance proposal handlers not available");
923
+ }
797
924
  const coreAddress = String(inputs.coreAddress || "").trim();
798
925
  if (!coreAddress) throw new Error("coreAddress is required");
799
926
  const operation = String(inputs.operation || "").trim();
@@ -861,6 +988,7 @@ registerAction({
861
988
  type: "qi/governance.settings-proposal",
862
989
  can: "governance/settings-proposal",
863
990
  sideEffect: true,
991
+ proof: { fields: ["proposalId"] },
864
992
  defaultRequiresConfirmation: true,
865
993
  requiredCapability: "flow/block/execute",
866
994
  outputSchema: [
@@ -875,6 +1003,9 @@ registerAction({
875
1003
  if (!handlers) {
876
1004
  throw new Error("Handlers not available");
877
1005
  }
1006
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
1007
+ throw new Error("Governance proposal handlers not available");
1008
+ }
878
1009
  const coreAddress = String(inputs.coreAddress || "").trim();
879
1010
  if (!coreAddress) throw new Error("coreAddress is required");
880
1011
  const votingPeriodHours = Number(inputs.votingPeriodHours);
@@ -937,6 +1068,7 @@ registerAction({
937
1068
  type: "qi/governance.transaction.send-funds",
938
1069
  can: "governance.transaction/send-funds",
939
1070
  sideEffect: true,
1071
+ proof: { fields: ["proposalId"] },
940
1072
  defaultRequiresConfirmation: true,
941
1073
  requiredCapability: "flow/block/execute",
942
1074
  outputSchema: [
@@ -952,6 +1084,9 @@ registerAction({
952
1084
  if (!handlers) {
953
1085
  throw new Error("Handlers not available");
954
1086
  }
1087
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
1088
+ throw new Error("Governance proposal handlers not available");
1089
+ }
955
1090
  const coreAddress = String(inputs.coreAddress || "").trim();
956
1091
  if (!coreAddress) throw new Error("coreAddress is required");
957
1092
  const recipient = String(inputs.recipient || "").trim();
@@ -995,6 +1130,7 @@ registerAction({
995
1130
  type: "qi/http.request",
996
1131
  can: "http/request",
997
1132
  sideEffect: false,
1133
+ proof: { fields: ["status"] },
998
1134
  defaultRequiresConfirmation: false,
999
1135
  // HTTP request can be triggered as a listener — a human assignee is DM'd
1000
1136
  // to invoke it when the upstream event fires. See §3.6 of the
@@ -1055,6 +1191,7 @@ registerAction({
1055
1191
  type: "qi/email.send",
1056
1192
  can: "email/send",
1057
1193
  sideEffect: true,
1194
+ proof: { fields: ["messageId"] },
1058
1195
  defaultRequiresConfirmation: true,
1059
1196
  requiredCapability: "email/send",
1060
1197
  // Email send is autonomous-enough to be triggered by another block's event.
@@ -1120,6 +1257,7 @@ registerAction({
1120
1257
  type: "qi/human.checkbox.set",
1121
1258
  can: "human/checkbox",
1122
1259
  sideEffect: true,
1260
+ proof: "none",
1123
1261
  defaultRequiresConfirmation: false,
1124
1262
  requiredCapability: "flow/execute",
1125
1263
  inputSchema: {
@@ -1161,6 +1299,7 @@ function registerFormSubmitAction(type, can) {
1161
1299
  type,
1162
1300
  can,
1163
1301
  sideEffect: true,
1302
+ proof: "none",
1164
1303
  defaultRequiresConfirmation: false,
1165
1304
  requiredCapability: "flow/execute",
1166
1305
  // Additive: makes the form an event SOURCE so downstream blocks can
@@ -1210,6 +1349,7 @@ registerAction({
1210
1349
  type: "qi/notification.push",
1211
1350
  can: "notification/push",
1212
1351
  sideEffect: true,
1352
+ proof: { fields: ["messageId"] },
1213
1353
  defaultRequiresConfirmation: true,
1214
1354
  requiredCapability: "notify/send",
1215
1355
  inputSchema: {
@@ -1266,6 +1406,7 @@ registerAction({
1266
1406
  type: "qi/bid.submit",
1267
1407
  can: "bid/submit",
1268
1408
  sideEffect: true,
1409
+ proof: { fields: ["bidId"] },
1269
1410
  defaultRequiresConfirmation: true,
1270
1411
  requiredCapability: "flow/block/execute",
1271
1412
  inputSchema: {
@@ -1349,6 +1490,7 @@ registerAction({
1349
1490
  type: "qi/bid.evaluate",
1350
1491
  can: "bid/evaluate",
1351
1492
  sideEffect: true,
1493
+ proof: { fields: ["bidId"] },
1352
1494
  defaultRequiresConfirmation: true,
1353
1495
  requiredCapability: "flow/block/execute",
1354
1496
  outputSchema: [
@@ -1495,7 +1637,7 @@ function extractSurveyAnswerSchema(survey) {
1495
1637
  if (!survey || typeof survey !== "object") return [];
1496
1638
  const json = survey;
1497
1639
  const collected = [];
1498
- const seen = /* @__PURE__ */ new Set();
1640
+ const seen2 = /* @__PURE__ */ new Set();
1499
1641
  const walk = (elements) => {
1500
1642
  if (!Array.isArray(elements)) return;
1501
1643
  for (const el of elements) {
@@ -1506,8 +1648,8 @@ function extractSurveyAnswerSchema(survey) {
1506
1648
  }
1507
1649
  const name = typeof el.name === "string" ? el.name.trim() : "";
1508
1650
  if (!name) continue;
1509
- if (seen.has(name)) continue;
1510
- seen.add(name);
1651
+ if (seen2.has(name)) continue;
1652
+ seen2.add(name);
1511
1653
  const rawType = typeof el.type === "string" ? el.type : "";
1512
1654
  const mapped = TYPE_MAP[rawType] ?? "string";
1513
1655
  let itemSchema;
@@ -1577,6 +1719,13 @@ registerAction({
1577
1719
  type: "qi/claim.submit",
1578
1720
  can: "claim/submit",
1579
1721
  sideEffect: true,
1722
+ // Dynamic resolvers predate merge-by-default and intentionally control
1723
+ // the full vocabulary (e.g. returning [] to hide the static baseline).
1724
+ dynamicResolutionMode: "replace",
1725
+ proof: { fields: ["claimId"] },
1726
+ // Repeatable: a collection accepts many claims over the flow's life (e.g. a
1727
+ // monthly submission). Never latches to a terminal Done. See cardinality.
1728
+ cardinality: "many",
1580
1729
  defaultRequiresConfirmation: true,
1581
1730
  requiredCapability: "flow/block/execute",
1582
1731
  eligibleForEventTrigger: true,
@@ -1947,7 +2096,7 @@ function serializeXeroPaymentCreateInputs(inputs) {
1947
2096
  return JSON.stringify(inputs);
1948
2097
  }
1949
2098
 
1950
- // src/mantine/utils/referenceResolver.ts
2099
+ // src/core/lib/flowEngine/referenceResolver.ts
1951
2100
  var REFERENCE_REGEX = /\{\{([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_.:]+)\}\}/g;
1952
2101
  function parseReferences(input) {
1953
2102
  const references = [];
@@ -1969,68 +2118,78 @@ function getNestedValue(obj, path) {
1969
2118
  return current?.[key];
1970
2119
  }, obj);
1971
2120
  }
1972
- function resolveSingleReference(blockId, propPath, editorDocument, yRuntime, scope) {
2121
+ function resolveSingleReferenceDetailed(blockId, propPath, editorDocument, yRuntime, scope) {
1973
2122
  if (scope && Object.prototype.hasOwnProperty.call(scope, blockId)) {
1974
2123
  const root = scope[blockId];
1975
- if (root == null) return void 0;
1976
- return getNestedValue(root, propPath);
2124
+ if (root == null) return { value: void 0, reason: "missing-value" };
2125
+ const value2 = getNestedValue(root, propPath);
2126
+ return { value: value2, reason: value2 === void 0 ? "missing-value" : void 0 };
1977
2127
  }
1978
2128
  if (!editorDocument || !Array.isArray(editorDocument)) {
1979
- return void 0;
2129
+ return { value: void 0, reason: "unknown-block" };
1980
2130
  }
1981
2131
  const block = editorDocument.find((b) => b.id === blockId);
1982
2132
  if (!block) {
1983
- return void 0;
2133
+ return { value: void 0, reason: "unknown-block" };
1984
2134
  }
1985
2135
  if (propPath.startsWith("output.")) {
1986
- if (!yRuntime) return void 0;
2136
+ if (!yRuntime) return { value: void 0, reason: "missing-value" };
1987
2137
  const runtimeState = yRuntime.get(blockId);
1988
- if (!runtimeState?.output) return void 0;
2138
+ if (!runtimeState?.output) return { value: void 0, reason: "missing-value" };
1989
2139
  const innerPath = propPath.substring("output.".length);
1990
2140
  const direct = getNestedValue(runtimeState.output, innerPath);
1991
- if (direct !== void 0) return direct;
2141
+ if (direct !== void 0) return { value: direct };
1992
2142
  if (runtimeState.output.data !== void 0) {
1993
- return getNestedValue(runtimeState.output.data, innerPath);
2143
+ const value2 = getNestedValue(runtimeState.output.data, innerPath);
2144
+ return { value: value2, reason: value2 === void 0 ? "missing-value" : void 0 };
1994
2145
  }
1995
2146
  if (runtimeState.output.http?.data !== void 0) {
1996
- return getNestedValue(runtimeState.output.http.data, innerPath);
2147
+ const value2 = getNestedValue(runtimeState.output.http.data, innerPath);
2148
+ return { value: value2, reason: value2 === void 0 ? "missing-value" : void 0 };
1997
2149
  }
1998
- return void 0;
2150
+ return { value: void 0, reason: "missing-value" };
1999
2151
  }
2000
2152
  if (propPath.startsWith("response.")) {
2001
2153
  const responseData = block.props.response;
2002
2154
  if (!responseData) {
2003
- return void 0;
2155
+ return { value: void 0, reason: "missing-value" };
2004
2156
  }
2005
2157
  try {
2006
2158
  const parsedResponse = typeof responseData === "string" ? JSON.parse(responseData) : responseData;
2007
2159
  const innerPath = propPath.substring("response.".length);
2008
2160
  const value2 = getNestedValue(parsedResponse, innerPath);
2009
- return value2;
2010
- } catch (error) {
2011
- return void 0;
2161
+ return { value: value2, reason: value2 === void 0 ? "missing-value" : void 0 };
2162
+ } catch {
2163
+ warnOnce(`ref-response-parse:${blockId}`, `[flow-config] block ${blockId}: props.response is not valid JSON; {{${blockId}.${propPath}}} cannot resolve`);
2164
+ return { value: void 0, reason: "response-parse-error" };
2012
2165
  }
2013
2166
  }
2014
2167
  const value = getNestedValue(block.props, propPath);
2015
- return value;
2168
+ return { value, reason: value === void 0 ? "missing-value" : void 0 };
2016
2169
  }
2017
- function resolveReferences(input, editorDocument, options = {}) {
2018
- const { fallback = "", stringifyObjects = true, yRuntime, scope } = options;
2170
+ function resolveSingleReference(blockId, propPath, editorDocument, yRuntime, scope) {
2171
+ return resolveSingleReferenceDetailed(blockId, propPath, editorDocument, yRuntime, scope).value;
2172
+ }
2173
+ function resolveReferencesDetailed(input, editorDocument, options = {}) {
2174
+ const { fallback = "", stringifyObjects = true, yRuntime, scope, warnContext } = options;
2175
+ const unresolved = [];
2019
2176
  if (input == null) {
2020
- return "";
2177
+ return { value: "", unresolved };
2021
2178
  }
2022
2179
  const inputStr = String(input);
2023
2180
  const references = parseReferences(inputStr);
2024
2181
  if (references.length === 0) {
2025
- return inputStr;
2182
+ return { value: inputStr, unresolved };
2026
2183
  }
2027
2184
  let result = inputStr;
2028
2185
  for (let i = references.length - 1; i >= 0; i--) {
2029
2186
  const ref = references[i];
2030
- const resolvedValue = resolveSingleReference(ref.blockId, ref.propPath, editorDocument, yRuntime, scope);
2187
+ const resolution = resolveSingleReferenceDetailed(ref.blockId, ref.propPath, editorDocument, yRuntime, scope);
2188
+ const resolvedValue = resolution.value;
2031
2189
  let replacementStr;
2032
2190
  if (resolvedValue === void 0 || resolvedValue === null) {
2033
2191
  replacementStr = fallback;
2192
+ unresolved.push({ ref: ref.fullMatch, blockId: ref.blockId, propPath: ref.propPath, reason: resolution.reason || "missing-value" });
2034
2193
  } else if (typeof resolvedValue === "object") {
2035
2194
  replacementStr = stringifyObjects ? JSON.stringify(resolvedValue) : fallback;
2036
2195
  } else {
@@ -2038,7 +2197,15 @@ function resolveReferences(input, editorDocument, options = {}) {
2038
2197
  }
2039
2198
  result = result.substring(0, ref.startIndex) + replacementStr + result.substring(ref.endIndex);
2040
2199
  }
2041
- return result;
2200
+ if (warnContext && unresolved.length > 0) {
2201
+ for (const entry of unresolved) {
2202
+ warnOnce(`ref-unresolved:${warnContext}:${entry.ref}`, `[flow-config] ${warnContext}: reference ${entry.ref} did not resolve (${entry.reason}); using fallback '${fallback}'`);
2203
+ }
2204
+ }
2205
+ return { value: result, unresolved };
2206
+ }
2207
+ function resolveReferences(input, editorDocument, options = {}) {
2208
+ return resolveReferencesDetailed(input, editorDocument, options).value;
2042
2209
  }
2043
2210
  function hasReferences(input) {
2044
2211
  if (input == null) return false;
@@ -2241,6 +2408,13 @@ registerAction({
2241
2408
  type: "qi/claim.evaluate",
2242
2409
  can: "claim/evaluate",
2243
2410
  sideEffect: true,
2411
+ // Dynamic resolvers predate merge-by-default and intentionally control
2412
+ // the full vocabulary (e.g. returning [] to hide the static baseline).
2413
+ dynamicResolutionMode: "replace",
2414
+ proof: { fields: ["claimId"] },
2415
+ // Repeatable: an evaluator processes many claims over the flow's life. Never
2416
+ // latches to a terminal Done. See cardinality.
2417
+ cardinality: "many",
2244
2418
  defaultRequiresConfirmation: true,
2245
2419
  requiredCapability: "flow/block/execute",
2246
2420
  // Static fallback — used until a collection is picked and the survey
@@ -2534,6 +2708,7 @@ registerAction({
2534
2708
  type: "qi/proposal.create",
2535
2709
  can: "proposal/create",
2536
2710
  sideEffect: true,
2711
+ proof: { fields: ["proposalId"] },
2537
2712
  defaultRequiresConfirmation: true,
2538
2713
  requiredCapability: "flow/block/execute",
2539
2714
  inputSchema: {
@@ -2558,6 +2733,9 @@ registerAction({
2558
2733
  if (!handlers) {
2559
2734
  throw new Error("Handlers not available");
2560
2735
  }
2736
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
2737
+ throw new Error("Governance proposal handlers not available");
2738
+ }
2561
2739
  const coreAddress = String(inputs.coreAddress || "").trim();
2562
2740
  const title = String(inputs.title || "").trim();
2563
2741
  const description = String(inputs.description || "").trim();
@@ -2611,6 +2789,7 @@ registerAction({
2611
2789
  type: "qi/proposal.vote",
2612
2790
  can: "proposal/vote",
2613
2791
  sideEffect: true,
2792
+ proof: { fields: ["votedAt"] },
2614
2793
  defaultRequiresConfirmation: true,
2615
2794
  requiredCapability: "flow/block/execute",
2616
2795
  inputSchema: {
@@ -2634,6 +2813,9 @@ registerAction({
2634
2813
  if (!handlers) {
2635
2814
  throw new Error("Handlers not available");
2636
2815
  }
2816
+ if (!handlers.vote) {
2817
+ throw new Error("vote handler not available");
2818
+ }
2637
2819
  const proposalId = Number(inputs.proposalId);
2638
2820
  const vote = String(inputs.vote || "").trim();
2639
2821
  const rationale = String(inputs.rationale || "").trim();
@@ -2667,6 +2849,7 @@ registerAction({
2667
2849
  type: "qi/protocol.select",
2668
2850
  can: "protocol/select",
2669
2851
  sideEffect: false,
2852
+ proof: "none",
2670
2853
  defaultRequiresConfirmation: false,
2671
2854
  inputSchema: {
2672
2855
  type: "object",
@@ -3946,6 +4129,7 @@ registerAction({
3946
4129
  type: "qi/domain.sign",
3947
4130
  can: "domain/sign",
3948
4131
  sideEffect: true,
4132
+ proof: { fields: ["entityDid"] },
3949
4133
  defaultRequiresConfirmation: true,
3950
4134
  requiredCapability: "flow/block/execute",
3951
4135
  eligibleForEventTrigger: true,
@@ -4096,6 +4280,7 @@ registerAction({
4096
4280
  if (typeof handlers.createDomain !== "function") throw new Error("createDomain handler not implemented");
4097
4281
  if (typeof handlers.createAddLinkedResourceMessage !== "function") throw new Error("createAddLinkedResourceMessage handler not implemented");
4098
4282
  if (typeof handlers.executeTransaction !== "function") throw new Error("executeTransaction handler not implemented");
4283
+ const { requestPin, signCredential, publicFileUpload } = handlers;
4099
4284
  const saveCheckpoint = (updates) => {
4100
4285
  checkpoint = {
4101
4286
  ...checkpoint,
@@ -4160,14 +4345,14 @@ registerAction({
4160
4345
  });
4161
4346
  let signedCredential;
4162
4347
  for (let attempt = 1; attempt <= MAX_PIN_ATTEMPTS; attempt++) {
4163
- const pin = await handlers.requestPin({
4348
+ const pin = await requestPin({
4164
4349
  title: "Sign Domain Card",
4165
4350
  description: attempt === 1 ? "Enter your PIN to sign the Domain Card credential" : "Incorrect PIN. Re-enter your PIN to sign the Domain Card credential.",
4166
4351
  submitText: "Sign"
4167
4352
  });
4168
4353
  if (!pin) throw new Error("PIN entry cancelled");
4169
4354
  try {
4170
- ({ signedCredential } = await handlers.signCredential({
4355
+ ({ signedCredential } = await signCredential({
4171
4356
  issuerDid,
4172
4357
  issuerType: "user",
4173
4358
  credential: unsignedCredential,
@@ -4186,7 +4371,7 @@ registerAction({
4186
4371
  const credentialFile = new File([credentialBlob], "domainCard.json", {
4187
4372
  type: "application/json"
4188
4373
  });
4189
- const uploadResult = await handlers.publicFileUpload(credentialFile);
4374
+ const uploadResult = await publicFileUpload(credentialFile);
4190
4375
  return {
4191
4376
  ...buildDomainCardLinkedResource({
4192
4377
  entityDid,
@@ -4360,6 +4545,7 @@ registerAction({
4360
4545
  type: "qi/domain.card-preview",
4361
4546
  can: "domain/card-preview",
4362
4547
  sideEffect: false,
4548
+ proof: "none",
4363
4549
  defaultRequiresConfirmation: false,
4364
4550
  inputSchema: {
4365
4551
  type: "object",
@@ -4439,6 +4625,7 @@ registerAction({
4439
4625
  type: "oracle",
4440
4626
  can: "oracle/query",
4441
4627
  sideEffect: false,
4628
+ proof: "none",
4442
4629
  defaultRequiresConfirmation: false,
4443
4630
  inputSchema: {
4444
4631
  type: "object",
@@ -4466,6 +4653,7 @@ registerAction({
4466
4653
  type: "qi/credential.store",
4467
4654
  can: "credential/store",
4468
4655
  sideEffect: true,
4656
+ proof: { fields: ["storedAt"] },
4469
4657
  defaultRequiresConfirmation: true,
4470
4658
  requiredCapability: "flow/execute",
4471
4659
  inputSchema: {
@@ -4527,6 +4715,7 @@ registerAction({
4527
4715
  type: "qi/payment.execute",
4528
4716
  can: "payment/execute",
4529
4717
  sideEffect: true,
4718
+ proof: { fields: ["paymentBlockId"] },
4530
4719
  defaultRequiresConfirmation: true,
4531
4720
  requiredCapability: "flow/block/execute",
4532
4721
  inputSchema: {
@@ -4611,6 +4800,7 @@ registerAction({
4611
4800
  type: "qi/matrix.dm",
4612
4801
  can: "matrix/dm",
4613
4802
  sideEffect: true,
4803
+ proof: { fields: ["roomId"] },
4614
4804
  defaultRequiresConfirmation: false,
4615
4805
  inputSchema: {
4616
4806
  type: "object",
@@ -4648,6 +4838,7 @@ registerAction({
4648
4838
  type: "qi/wallet.generate",
4649
4839
  can: "wallet/generate",
4650
4840
  sideEffect: false,
4841
+ proof: { fields: ["address"] },
4651
4842
  defaultRequiresConfirmation: false,
4652
4843
  inputSchema: {
4653
4844
  type: "object",
@@ -4674,6 +4865,7 @@ registerAction({
4674
4865
  type: "qi/wallet.fund",
4675
4866
  can: "wallet/fund",
4676
4867
  sideEffect: true,
4868
+ proof: { fields: ["transactionHash"] },
4677
4869
  defaultRequiresConfirmation: true,
4678
4870
  inputSchema: {
4679
4871
  type: "object",
@@ -4702,6 +4894,7 @@ registerAction({
4702
4894
  type: "qi/wallet.generateAndFund",
4703
4895
  can: "wallet/generateAndFund",
4704
4896
  sideEffect: true,
4897
+ proof: { fields: ["transactionHash"] },
4705
4898
  defaultRequiresConfirmation: false,
4706
4899
  inputSchema: {
4707
4900
  type: "object",
@@ -4752,6 +4945,7 @@ registerAction({
4752
4945
  type: "qi/iid.create",
4753
4946
  can: "iid/create",
4754
4947
  sideEffect: true,
4948
+ proof: { fields: ["transactionHash", "alreadyExisted"] },
4755
4949
  defaultRequiresConfirmation: false,
4756
4950
  inputSchema: {
4757
4951
  type: "object",
@@ -4790,6 +4984,7 @@ registerAction({
4790
4984
  type: "qi/matrix.register",
4791
4985
  can: "matrix/register",
4792
4986
  sideEffect: true,
4987
+ proof: { fields: ["matrixUserId"] },
4793
4988
  defaultRequiresConfirmation: false,
4794
4989
  inputSchema: {
4795
4990
  type: "object",
@@ -4839,6 +5034,8 @@ registerAction({
4839
5034
  type: "qi/identity.create",
4840
5035
  can: "identity/create",
4841
5036
  sideEffect: true,
5037
+ // Dual-phase proof: on-chain IID (transactionHash or alreadyExisted) AND matrix registration.
5038
+ proof: { validate: (output) => !!(output.transactionHash || output.alreadyExisted) && !!output.matrixUserId },
4842
5039
  defaultRequiresConfirmation: false,
4843
5040
  inputSchema: {
4844
5041
  type: "object",
@@ -4939,6 +5136,7 @@ registerAction({
4939
5136
  type: "qi/entity.createOracle",
4940
5137
  can: "entity/createOracle",
4941
5138
  sideEffect: true,
5139
+ proof: { fields: ["entityDid"] },
4942
5140
  defaultRequiresConfirmation: false,
4943
5141
  inputSchema: {
4944
5142
  type: "object",
@@ -5041,6 +5239,7 @@ registerAction({
5041
5239
  type: "qi/sandbox.provision",
5042
5240
  can: "sandbox/provision",
5043
5241
  sideEffect: true,
5242
+ proof: { fields: ["status", "sandboxUrl"] },
5044
5243
  defaultRequiresConfirmation: false,
5045
5244
  inputSchema: {
5046
5245
  type: "object",
@@ -5073,6 +5272,7 @@ registerAction({
5073
5272
  type: "qi/oracle.contract",
5074
5273
  can: "oracle/contract",
5075
5274
  sideEffect: true,
5275
+ proof: { fields: ["userOracleRoomId"] },
5076
5276
  defaultRequiresConfirmation: false,
5077
5277
  inputSchema: {
5078
5278
  type: "object",
@@ -5103,6 +5303,7 @@ registerAction({
5103
5303
  type: "qi/oracle.storeSecrets",
5104
5304
  can: "oracle/storeSecrets",
5105
5305
  sideEffect: true,
5306
+ proof: { fields: ["storedSecrets"] },
5106
5307
  defaultRequiresConfirmation: false,
5107
5308
  inputSchema: {
5108
5309
  type: "object",
@@ -5193,6 +5394,7 @@ registerAction({
5193
5394
  type: "qi/oracle.storeConfig",
5194
5395
  can: "oracle/storeConfig",
5195
5396
  sideEffect: true,
5397
+ proof: { fields: ["configStored"] },
5196
5398
  defaultRequiresConfirmation: false,
5197
5399
  inputSchema: {
5198
5400
  type: "object",
@@ -5263,6 +5465,7 @@ registerAction({
5263
5465
  type: "qi/oracle.storeSecretsAndConfig",
5264
5466
  can: "oracle/storeSecretsAndConfig",
5265
5467
  sideEffect: true,
5468
+ proof: { fields: ["storedSecrets"] },
5266
5469
  defaultRequiresConfirmation: false,
5267
5470
  inputSchema: {
5268
5471
  type: "object",
@@ -5414,6 +5617,7 @@ registerAction({
5414
5617
  type: "qi/oracle.configureOracle",
5415
5618
  can: "oracle/configureOracle",
5416
5619
  sideEffect: true,
5620
+ proof: { fields: ["configStored"] },
5417
5621
  defaultRequiresConfirmation: false,
5418
5622
  inputSchema: {
5419
5623
  type: "object",
@@ -5594,6 +5798,7 @@ registerAction({
5594
5798
  type: "qi/oracle.deploySetup",
5595
5799
  can: "oracle/deploySetup",
5596
5800
  sideEffect: true,
5801
+ proof: { fields: ["setupComplete"] },
5597
5802
  defaultRequiresConfirmation: false,
5598
5803
  inputSchema: {
5599
5804
  type: "object",
@@ -5628,6 +5833,7 @@ registerAction({
5628
5833
  type: "qi/oracle.deployStart",
5629
5834
  can: "oracle/deployStart",
5630
5835
  sideEffect: true,
5836
+ proof: { fields: ["processId", "status"] },
5631
5837
  defaultRequiresConfirmation: false,
5632
5838
  inputSchema: {
5633
5839
  type: "object",
@@ -5661,6 +5867,7 @@ registerAction({
5661
5867
  type: "qi/oracle.deploy",
5662
5868
  can: "oracle/deploy",
5663
5869
  sideEffect: true,
5870
+ proof: { fields: ["processId", "status"] },
5664
5871
  defaultRequiresConfirmation: false,
5665
5872
  inputSchema: {
5666
5873
  type: "object",
@@ -5796,6 +6003,10 @@ registerAction({
5796
6003
  type: COLLECTION_LIFECYCLE_ACTION_TYPE,
5797
6004
  can: "collection/lifecycle",
5798
6005
  sideEffect: true,
6006
+ // Dynamic resolvers predate merge-by-default and intentionally control
6007
+ // the full vocabulary (e.g. returning [] to hide the static baseline).
6008
+ dynamicResolutionMode: "replace",
6009
+ proof: { fields: ["collectionId"] },
5799
6010
  defaultRequiresConfirmation: true,
5800
6011
  requiredCapability: "flow/block/execute",
5801
6012
  outputSchema: COLLECTION_LIFECYCLE_OUTPUT_SCHEMA,
@@ -5992,6 +6203,13 @@ registerAction({
5992
6203
  type: COLLECTION_USERS_ACTION_TYPE,
5993
6204
  can: "collection/users",
5994
6205
  sideEffect: true,
6206
+ // Dynamic resolvers predate merge-by-default and intentionally control
6207
+ // the full vocabulary (e.g. returning [] to hide the static baseline).
6208
+ dynamicResolutionMode: "replace",
6209
+ proof: { fields: ["transactionHash", "collectionId"] },
6210
+ // Repeatable: members are granted/revoked many times over the flow's life.
6211
+ // Never latches to a terminal Done. See cardinality.
6212
+ cardinality: "many",
5995
6213
  defaultRequiresConfirmation: true,
5996
6214
  requiredCapability: "flow/block/execute",
5997
6215
  outputSchema: COLLECTION_USERS_OUTPUT_SCHEMA,
@@ -6155,6 +6373,7 @@ registerAction({
6155
6373
  type: "qi/carbon.loadBatches",
6156
6374
  can: "carbon/load",
6157
6375
  sideEffect: false,
6376
+ proof: "none",
6158
6377
  defaultRequiresConfirmation: false,
6159
6378
  inputSchema: {
6160
6379
  type: "object",
@@ -6181,6 +6400,7 @@ registerAction({
6181
6400
  type: "qi/carbon.harvest",
6182
6401
  can: "carbon/harvest",
6183
6402
  sideEffect: true,
6403
+ proof: { fields: ["transactionHash"] },
6184
6404
  defaultRequiresConfirmation: true,
6185
6405
  inputSchema: {
6186
6406
  type: "object",
@@ -6220,6 +6440,7 @@ registerAction({
6220
6440
  type: "qi/carbon.retire",
6221
6441
  can: "carbon/retire",
6222
6442
  sideEffect: true,
6443
+ proof: { fields: ["transactionHash"] },
6223
6444
  defaultRequiresConfirmation: true,
6224
6445
  inputSchema: {
6225
6446
  type: "object",
@@ -6281,6 +6502,7 @@ registerAction({
6281
6502
  type: "qi/entity.transfer",
6282
6503
  can: "entity/transfer",
6283
6504
  sideEffect: true,
6505
+ proof: { fields: ["transactionHash"] },
6284
6506
  defaultRequiresConfirmation: true,
6285
6507
  outputSchema: ENTITY_TRANSFER_OUTPUT,
6286
6508
  inputSchema: {
@@ -6426,6 +6648,8 @@ registerAction({
6426
6648
  type: "qi/gmail.email.send",
6427
6649
  can: "gmail.email/send",
6428
6650
  sideEffect: true,
6651
+ // Proof of execution: Gmail returns the sent message id. Matches qi/email.send.
6652
+ proof: { fields: ["messageId"] },
6429
6653
  defaultRequiresConfirmation: true,
6430
6654
  requiredCapability: "flow/block/execute",
6431
6655
  // Can be wired to another block's event (e.g. form submitted → send email).
@@ -6498,6 +6722,11 @@ registerAction({
6498
6722
  type: "qi/outlook.email.send",
6499
6723
  can: "outlook.email/send",
6500
6724
  sideEffect: true,
6725
+ // Outlook's send tool often returns no message id (see run() below), so there
6726
+ // is no reliable output field to prove execution — a successful tool call
6727
+ // (run() throws on failure) is the signal. Declared 'none' rather than
6728
+ // requiring messageId, which would push every id-less send to needs_verification.
6729
+ proof: "none",
6501
6730
  defaultRequiresConfirmation: true,
6502
6731
  requiredCapability: "flow/block/execute",
6503
6732
  // Can be wired to another block's event (e.g. form submitted → send email).
@@ -6570,6 +6799,8 @@ registerAction({
6570
6799
  type: "qi/slack.message.send",
6571
6800
  can: "slack.message/send",
6572
6801
  sideEffect: true,
6802
+ // Proof of execution: Slack returns the posted message timestamp (ts).
6803
+ proof: { fields: ["messageTs"] },
6573
6804
  defaultRequiresConfirmation: true,
6574
6805
  requiredCapability: "flow/block/execute",
6575
6806
  // Can be wired to another block's event (e.g. form submitted → post message).
@@ -6645,6 +6876,8 @@ registerAction({
6645
6876
  type: "qi/googlecalendar.event.create",
6646
6877
  can: "googlecalendar.event/create",
6647
6878
  sideEffect: true,
6879
+ // Proof of execution: the created event's id. Matches qi/calendar.event.create.
6880
+ proof: { fields: ["eventId"] },
6648
6881
  defaultRequiresConfirmation: true,
6649
6882
  requiredCapability: "flow/block/execute",
6650
6883
  eligibleForEventTrigger: true,
@@ -6741,6 +6974,7 @@ registerAction({
6741
6974
  type: "qi/calendar.event.create",
6742
6975
  can: "calendar.event/create",
6743
6976
  sideEffect: true,
6977
+ proof: { fields: ["eventId"] },
6744
6978
  defaultRequiresConfirmation: true,
6745
6979
  requiredCapability: "flow/block/execute",
6746
6980
  eligibleForEventTrigger: true,
@@ -6848,6 +7082,7 @@ registerAction({
6848
7082
  type: "qi/calendar.event.update",
6849
7083
  can: "calendar.event/update",
6850
7084
  sideEffect: true,
7085
+ proof: { fields: ["eventId"] },
6851
7086
  defaultRequiresConfirmation: true,
6852
7087
  requiredCapability: "flow/block/execute",
6853
7088
  eligibleForEventTrigger: true,
@@ -6957,6 +7192,7 @@ registerAction({
6957
7192
  type: "qi/calendar.event.list",
6958
7193
  can: "calendar.event/list",
6959
7194
  sideEffect: false,
7195
+ proof: "none",
6960
7196
  defaultRequiresConfirmation: false,
6961
7197
  requiredCapability: "flow/block/execute",
6962
7198
  eligibleForEventTrigger: false,
@@ -7038,6 +7274,7 @@ registerAction({
7038
7274
  type: "qi/xero.contact.create",
7039
7275
  can: "xero.contact/create",
7040
7276
  sideEffect: true,
7277
+ proof: { fields: ["contactId"] },
7041
7278
  defaultRequiresConfirmation: true,
7042
7279
  requiredCapability: "flow/block/execute",
7043
7280
  eligibleForEventTrigger: true,
@@ -7136,6 +7373,10 @@ registerAction({
7136
7373
  type: "qi/xero.invoice.create",
7137
7374
  can: "xero.invoice/create",
7138
7375
  sideEffect: true,
7376
+ proof: { fields: ["invoiceId"] },
7377
+ // Repeatable: many invoices are created over the flow's life. Never latches
7378
+ // to a terminal Done. See cardinality.
7379
+ cardinality: "many",
7139
7380
  defaultRequiresConfirmation: true,
7140
7381
  requiredCapability: "flow/block/execute",
7141
7382
  eligibleForEventTrigger: true,
@@ -7241,6 +7482,7 @@ registerAction({
7241
7482
  type: "qi/xero.invoice.list",
7242
7483
  can: "xero.invoice/list",
7243
7484
  sideEffect: false,
7485
+ proof: "none",
7244
7486
  defaultRequiresConfirmation: false,
7245
7487
  requiredCapability: "flow/block/execute",
7246
7488
  eligibleForEventTrigger: false,
@@ -7321,6 +7563,10 @@ registerAction({
7321
7563
  type: "qi/xero.payment.create",
7322
7564
  can: "xero.payment/create",
7323
7565
  sideEffect: true,
7566
+ proof: { fields: ["paymentId"] },
7567
+ // Repeatable: many payments are created over the flow's life. Never latches
7568
+ // to a terminal Done. See cardinality.
7569
+ cardinality: "many",
7324
7570
  defaultRequiresConfirmation: true,
7325
7571
  requiredCapability: "flow/block/execute",
7326
7572
  eligibleForEventTrigger: true,
@@ -9031,175 +9277,285 @@ var createUcanService = (config) => {
9031
9277
  };
9032
9278
  };
9033
9279
 
9034
- // src/core/lib/flowEngine/utils.ts
9035
- var buildAuthzFromProps = (props) => {
9036
- const linkedClaimCollectionId = typeof props.linkedClaimCollectionId === "string" ? props.linkedClaimCollectionId.trim() : "";
9037
- const authz = {};
9038
- if (linkedClaimCollectionId) {
9039
- authz.linkedClaim = { collectionId: linkedClaimCollectionId };
9040
- }
9041
- return authz;
9042
- };
9043
- var buildFlowNodeFromBlock = (block) => {
9044
- const base = {
9045
- id: block.id,
9046
- type: block.type,
9047
- props: block.props || {}
9048
- };
9049
- const authz = buildAuthzFromProps(block.props || {});
9050
- return {
9051
- ...base,
9052
- ...authz
9053
- };
9054
- };
9055
-
9056
- // src/core/lib/flowEngine/runtime.ts
9057
- var XERO_WORK_ITEMS_MAP_NAME = "xeroWorkItems";
9058
- var XERO_CONNECTION_MAP_NAME = "xeroConnection";
9059
- var ensureStateObject = (value) => {
9060
- if (!value || typeof value !== "object") {
9061
- return {};
9062
- }
9063
- return { ...value };
9064
- };
9065
- var createYMapManager = (map) => {
9066
- return {
9067
- get: (nodeId) => {
9068
- const stored = map.get(nodeId);
9069
- return ensureStateObject(stored);
9070
- },
9071
- update: (nodeId, updates) => {
9072
- const current = ensureStateObject(map.get(nodeId));
9073
- map.set(nodeId, { ...current, ...updates });
9074
- }
9075
- };
9076
- };
9077
- var createMemoryManager = () => {
9078
- const memory = /* @__PURE__ */ new Map();
9079
- return {
9080
- get: (nodeId) => ensureStateObject(memory.get(nodeId)),
9081
- update: (nodeId, updates) => {
9082
- const current = ensureStateObject(memory.get(nodeId));
9083
- memory.set(nodeId, { ...current, ...updates });
9084
- }
9085
- };
9086
- };
9087
- var createRuntimeStateManager = (editor) => {
9088
- if (editor?._yRuntime) {
9089
- return createYMapManager(editor._yRuntime);
9090
- }
9091
- return createMemoryManager();
9092
- };
9093
- var createYDocRuntimeManager = (yDoc) => {
9094
- return createYMapManager(yDoc.getMap("runtime"));
9095
- };
9096
- function clearRuntimeForTemplateClone(yDoc) {
9097
- const runtime = yDoc.getMap("runtime");
9098
- const invocations = yDoc.getMap("invocations");
9099
- const pendingInvocations = yDoc.getMap("pendingInvocations");
9100
- const agentOutbox = yDoc.getMap("agentOutbox");
9101
- const agentLeases = yDoc.getMap("agentLeases");
9102
- const auditTrail = yDoc.getMap("auditTrail");
9103
- const xeroWorkItems = yDoc.getMap(XERO_WORK_ITEMS_MAP_NAME);
9104
- const xeroConnection = yDoc.getMap(XERO_CONNECTION_MAP_NAME);
9105
- yDoc.transact(() => {
9106
- runtime.forEach((_, key) => runtime.delete(key));
9107
- invocations.forEach((_, key) => invocations.delete(key));
9108
- pendingInvocations.forEach((_, key) => pendingInvocations.delete(key));
9109
- agentOutbox.forEach((_, key) => agentOutbox.delete(key));
9110
- agentLeases.forEach((_, key) => agentLeases.delete(key));
9111
- auditTrail.forEach((_, key) => auditTrail.delete(key));
9112
- xeroWorkItems.forEach((_, key) => xeroWorkItems.delete(key));
9113
- xeroConnection.forEach((_, key) => xeroConnection.delete(key));
9114
- });
9115
- }
9116
-
9117
9280
  // src/core/types/baseUcan.ts
9118
9281
  function isRuntimeRef(value) {
9119
9282
  return typeof value === "object" && value !== null && "$ref" in value && typeof value.$ref === "string";
9120
9283
  }
9121
9284
 
9122
- // src/core/lib/flowCompiler/resolveRefs.ts
9123
- function resolveRuntimeRefs(nb, getNodeOutput2, triggerContext) {
9124
- return resolveValue(nb, getNodeOutput2, triggerContext);
9285
+ // src/core/lib/flowEngine/triggers.ts
9286
+ import * as Y from "yjs";
9287
+ var RUN_RECORD_AUDIT_TYPE = "block.run";
9288
+ function computePendingInvocationId(args) {
9289
+ const { sourceBlockId, sourceRunId, listenerBlockId, eventName, eventIndex } = args;
9290
+ const input = `${sourceBlockId}:${sourceRunId}:${listenerBlockId}:${eventName}:${eventIndex}`;
9291
+ return `pi-${fnv1a32(input)}`;
9125
9292
  }
9126
- function resolveValue(value, getNodeOutput2, triggerContext) {
9293
+ function snapshotInputRefs(inputs, getNodeOutput2) {
9294
+ const snapshots = {};
9295
+ walkRefs(inputs, (ref) => {
9296
+ if (ref.$ref.startsWith("trigger.")) return;
9297
+ const parsed = parseOutputRef(ref.$ref);
9298
+ if (!parsed) return;
9299
+ const output = getNodeOutput2(parsed.nodeId);
9300
+ if (!output) return;
9301
+ snapshots[ref.$ref] = getNestedValue(output, parsed.fieldPath);
9302
+ });
9303
+ return snapshots;
9304
+ }
9305
+ function walkRefs(value, visit) {
9127
9306
  if (isRuntimeRef(value)) {
9128
- return resolveRef(value.$ref, getNodeOutput2, triggerContext);
9307
+ visit(value);
9308
+ return;
9129
9309
  }
9130
9310
  if (Array.isArray(value)) {
9131
- return value.map((item) => resolveValue(item, getNodeOutput2, triggerContext));
9311
+ for (const item of value) walkRefs(item, visit);
9312
+ return;
9132
9313
  }
9133
9314
  if (typeof value === "object" && value !== null) {
9134
- const result = {};
9135
- for (const [key, val] of Object.entries(value)) {
9136
- result[key] = resolveValue(val, getNodeOutput2, triggerContext);
9137
- }
9138
- return result;
9315
+ for (const v of Object.values(value)) walkRefs(v, visit);
9139
9316
  }
9140
- return value;
9141
9317
  }
9142
- function resolveRef(ref, getNodeOutput2, triggerContext) {
9143
- if (ref.startsWith("trigger.payload.")) {
9144
- if (!triggerContext) {
9145
- throw new Error(`Trigger ref "${ref}" used outside of a listener invocation context. trigger.payload.* refs are only valid on block.event-triggered blocks.`);
9146
- }
9147
- const fieldPath2 = ref.slice("trigger.payload.".length);
9148
- return getNestedValue2(triggerContext.payload, fieldPath2);
9149
- }
9150
- if (triggerContext && Object.prototype.hasOwnProperty.call(triggerContext.refSnapshots, ref)) {
9151
- return triggerContext.refSnapshots[ref];
9152
- }
9318
+ function parseOutputRef(ref) {
9153
9319
  const outputIndex = ref.indexOf(".output.");
9154
- if (outputIndex === -1) {
9155
- throw new Error(`Invalid runtime reference "${ref}". Expected format: "nodeId.output.fieldPath" or "trigger.payload.fieldPath"`);
9320
+ if (outputIndex === -1) return null;
9321
+ return {
9322
+ nodeId: ref.slice(0, outputIndex),
9323
+ fieldPath: ref.slice(outputIndex + ".output.".length)
9324
+ };
9325
+ }
9326
+ function fnv1a32(input) {
9327
+ let hash = 2166136261;
9328
+ for (let i = 0; i < input.length; i++) {
9329
+ hash ^= input.charCodeAt(i);
9330
+ hash = Math.imul(hash, 16777619);
9156
9331
  }
9157
- const nodeId = ref.slice(0, outputIndex);
9158
- const fieldPath = ref.slice(outputIndex + ".output.".length);
9159
- const output = getNodeOutput2(nodeId);
9160
- if (!output) {
9161
- return void 0;
9332
+ return (hash >>> 0).toString(16).padStart(8, "0");
9333
+ }
9334
+ var PENDING_INVOCATIONS_MAP_KEY = "pendingInvocations";
9335
+ function getPendingInvocationsMap(yDoc) {
9336
+ return yDoc.getMap(PENDING_INVOCATIONS_MAP_KEY);
9337
+ }
9338
+ function getOrCreateBlockPendingMap(yDoc, blockId) {
9339
+ const outer = getPendingInvocationsMap(yDoc);
9340
+ let inner = outer.get(blockId);
9341
+ if (!inner) {
9342
+ inner = new Y.Map();
9343
+ outer.set(blockId, inner);
9162
9344
  }
9163
- return getNestedValue2(output, fieldPath);
9345
+ return inner;
9164
9346
  }
9165
- function getNestedValue2(obj, path) {
9166
- const parts = path.split(".");
9167
- let current = obj;
9168
- for (const part of parts) {
9169
- if (current == null || typeof current !== "object") {
9170
- return void 0;
9347
+ function readPendingInvocations(yDoc, blockId) {
9348
+ const outer = getPendingInvocationsMap(yDoc);
9349
+ const inner = outer.get(blockId);
9350
+ if (!inner) return [];
9351
+ const items = [];
9352
+ inner.forEach((value) => {
9353
+ if (value && typeof value === "object") {
9354
+ items.push(value);
9171
9355
  }
9172
- current = current[part];
9356
+ });
9357
+ items.sort((a, b) => a.emittedAt.localeCompare(b.emittedAt));
9358
+ return items;
9359
+ }
9360
+ function queuePendingInvocation(yDoc, listenerBlockId, invocation) {
9361
+ let created = false;
9362
+ yDoc.transact(() => {
9363
+ const inner = getOrCreateBlockPendingMap(yDoc, listenerBlockId);
9364
+ if (inner.has(invocation.id)) return;
9365
+ inner.set(invocation.id, invocation);
9366
+ created = true;
9367
+ });
9368
+ return created;
9369
+ }
9370
+ function removePendingInvocation(yDoc, listenerBlockId, pendingInvocationId) {
9371
+ const outer = getPendingInvocationsMap(yDoc);
9372
+ const inner = outer.get(listenerBlockId);
9373
+ if (!inner) return false;
9374
+ if (!inner.has(pendingInvocationId)) return false;
9375
+ inner.delete(pendingInvocationId);
9376
+ return true;
9377
+ }
9378
+ function readConsumedPendingInvocationIds(yDoc, listenerBlockId) {
9379
+ const consumed = /* @__PURE__ */ new Set();
9380
+ for (const record of readRunRecords(yDoc, listenerBlockId)) {
9381
+ if (record.fromPendingInvocationId) consumed.add(record.fromPendingInvocationId);
9173
9382
  }
9174
- return current;
9383
+ return consumed;
9175
9384
  }
9176
-
9177
- // src/core/lib/flowEngine/versionManifest.ts
9178
- var VERSION_MANIFEST = {
9179
- "0.3": {
9180
- version: "0.3",
9181
- label: "Legacy",
9182
- ucanRequired: false,
9183
- delegationRootRequired: false,
9184
- whitelistOnlyAllowed: true,
9185
- unrestrictedAllowed: true,
9186
- executionPath: "legacy",
9187
- authorizationFn: "v1",
9188
- allowedAuthModes: ["anyone", "actors", "capability"],
9189
- ui: {
9190
- showDelegationPanel: false,
9191
- showWhitelistConfig: true,
9192
- showAnyoneConfig: true,
9193
- showMigrationBanner: true,
9194
- requirePinForExecution: false
9195
- },
9385
+ function sweepExpiredPendingInvocations(yDoc, now = Date.now()) {
9386
+ const outer = getPendingInvocationsMap(yDoc);
9387
+ let removed = 0;
9388
+ outer.forEach((inner, listenerBlockId) => {
9389
+ const innerMap = inner;
9390
+ if (!innerMap || typeof innerMap.forEach !== "function") return;
9391
+ const expiredIds = [];
9392
+ innerMap.forEach((value, id) => {
9393
+ const expiresAt = value?.expiresAt;
9394
+ if (expiresAt && Date.parse(expiresAt) < now) expiredIds.push(id);
9395
+ });
9396
+ for (const id of expiredIds) {
9397
+ innerMap.delete(id);
9398
+ removed += 1;
9399
+ }
9400
+ void listenerBlockId;
9401
+ });
9402
+ return removed;
9403
+ }
9404
+ var BARRIER_STATE_MAP_KEY = "barrierState";
9405
+ function getBarrierStateMap(yDoc) {
9406
+ return yDoc.getMap(BARRIER_STATE_MAP_KEY);
9407
+ }
9408
+ function getOrCreateListenerBarrierMap(yDoc, listenerBlockId) {
9409
+ const outer = getBarrierStateMap(yDoc);
9410
+ let inner = outer.get(listenerBlockId);
9411
+ if (!inner) {
9412
+ inner = new Y.Map();
9413
+ outer.set(listenerBlockId, inner);
9414
+ }
9415
+ return inner;
9416
+ }
9417
+ function recordBarrierEvent(yDoc, listenerBlockId, entry) {
9418
+ let written = false;
9419
+ yDoc.transact(() => {
9420
+ const inner = getOrCreateListenerBarrierMap(yDoc, listenerBlockId);
9421
+ const key = `${entry.sourceBlockId}::${entry.eventName}`;
9422
+ const existing = inner.get(key);
9423
+ if (existing && typeof existing === "object" && existing.runId === entry.runId) return;
9424
+ inner.set(key, entry);
9425
+ written = true;
9426
+ });
9427
+ return written;
9428
+ }
9429
+ function readBarrierState(yDoc, listenerBlockId) {
9430
+ const outer = getBarrierStateMap(yDoc);
9431
+ const inner = outer.get(listenerBlockId);
9432
+ if (!inner) return [];
9433
+ const entries = [];
9434
+ inner.forEach((value) => {
9435
+ if (value && typeof value === "object") {
9436
+ entries.push(value);
9437
+ }
9438
+ });
9439
+ return entries;
9440
+ }
9441
+ function clearBarrierState(yDoc, listenerBlockId) {
9442
+ const outer = getBarrierStateMap(yDoc);
9443
+ yDoc.transact(() => {
9444
+ outer.delete(listenerBlockId);
9445
+ });
9446
+ }
9447
+ function computeBarrierInvocationId(entries, listenerBlockId) {
9448
+ const sorted = [...entries].sort((a, b) => a.sourceBlockId.localeCompare(b.sourceBlockId));
9449
+ const input = sorted.map((e) => `${e.sourceBlockId}:${e.runId}:${e.eventName}`).join("|") + `|>${listenerBlockId}`;
9450
+ return `bi-${fnv1a32(input)}`;
9451
+ }
9452
+ function mergeBarrierPayloads(entries) {
9453
+ const merged = {};
9454
+ for (const entry of entries) {
9455
+ if (entry.alias) {
9456
+ merged[entry.alias] = entry.payload;
9457
+ } else {
9458
+ Object.assign(merged, entry.payload);
9459
+ }
9460
+ }
9461
+ return merged;
9462
+ }
9463
+ function appendRunRecord(yDoc, blockId, details, userId) {
9464
+ const auditMap = yDoc.getMap("auditTrail");
9465
+ yDoc.transact(() => {
9466
+ let arr = auditMap.get(blockId);
9467
+ if (!arr) {
9468
+ arr = new Y.Array();
9469
+ auditMap.set(blockId, arr);
9470
+ }
9471
+ const event = {
9472
+ id: `${details.runId}`,
9473
+ blockId,
9474
+ type: RUN_RECORD_AUDIT_TYPE,
9475
+ details,
9476
+ message: void 0,
9477
+ meta: {
9478
+ timestamp: details.completedAt,
9479
+ userId,
9480
+ editable: false
9481
+ }
9482
+ };
9483
+ arr.push([event]);
9484
+ });
9485
+ }
9486
+ function readRunRecords(yDoc, blockId) {
9487
+ const auditMap = yDoc.getMap("auditTrail");
9488
+ const arr = auditMap.get(blockId);
9489
+ if (!arr) return [];
9490
+ const records = [];
9491
+ arr.forEach((entry) => {
9492
+ if (!entry || typeof entry !== "object") return;
9493
+ const e = entry;
9494
+ if (e.type === RUN_RECORD_AUDIT_TYPE && e.details) {
9495
+ records.push(e.details);
9496
+ }
9497
+ });
9498
+ return records;
9499
+ }
9500
+ function findFailedListenersForSourceRun(yDoc, sourceBlockId, sourceRunId, listenerBlockIds) {
9501
+ const failures = [];
9502
+ for (const listenerBlockId of listenerBlockIds) {
9503
+ const records = readRunRecords(yDoc, listenerBlockId);
9504
+ for (const record of records) {
9505
+ if (!record.error) continue;
9506
+ if (record.triggeredBy?.sourceBlockId !== sourceBlockId) continue;
9507
+ if (record.fromPendingInvocationId == null) continue;
9508
+ const sourceRunIdField = record.sourceRunId;
9509
+ if (sourceRunIdField && sourceRunIdField !== sourceRunId) continue;
9510
+ failures.push({ listenerBlockId, record });
9511
+ }
9512
+ }
9513
+ return failures;
9514
+ }
9515
+ function replayFailedListenerRun(yDoc, failedRecord, listenerBlockId, originalPayload, originalRefSnapshots, assigneeDid) {
9516
+ if (!failedRecord.triggeredBy) return false;
9517
+ const replayId = `${failedRecord.runId}:replay-${Date.now().toString(36)}`;
9518
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9519
+ const replay = {
9520
+ id: replayId,
9521
+ triggeringBlockId: failedRecord.triggeredBy.sourceBlockId,
9522
+ sourceRunId: failedRecord.sourceRunId || failedRecord.runId,
9523
+ eventName: failedRecord.triggeredBy.eventName,
9524
+ eventIndex: 0,
9525
+ payload: originalPayload,
9526
+ refSnapshots: originalRefSnapshots,
9527
+ assigneeDid,
9528
+ emittedAt: now,
9529
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString()
9530
+ };
9531
+ return queuePendingInvocation(yDoc, listenerBlockId, replay);
9532
+ }
9533
+
9534
+ // src/core/lib/flowEngine/versionManifest.ts
9535
+ var VERSION_MANIFEST = {
9536
+ "0.3": {
9537
+ version: "0.3",
9538
+ label: "Legacy",
9539
+ ucanRequired: false,
9540
+ delegationRootRequired: false,
9541
+ whitelistOnlyAllowed: true,
9542
+ unrestrictedAllowed: true,
9543
+ executionPath: "legacy",
9544
+ authorizationFn: "v1",
9545
+ allowedAuthModes: ["anyone", "actors", "capability"],
9546
+ ui: {
9547
+ showDelegationPanel: false,
9548
+ showWhitelistConfig: true,
9549
+ showAnyoneConfig: true,
9550
+ showMigrationBanner: true,
9551
+ requirePinForExecution: false
9552
+ },
9196
9553
  description: "Legacy version. UCAN optional, whitelist-only authorization accepted."
9197
9554
  },
9198
9555
  "1.0.0": {
9199
9556
  version: "1.0.0",
9200
9557
  label: "UCAN Required",
9201
- /** TEMP Disablement - needs to be true TODO */
9202
- ucanRequired: false,
9558
+ ucanRequired: true,
9203
9559
  delegationRootRequired: true,
9204
9560
  whitelistOnlyAllowed: false,
9205
9561
  unrestrictedAllowed: false,
@@ -9217,6 +9573,7 @@ var VERSION_MANIFEST = {
9217
9573
  }
9218
9574
  };
9219
9575
  var LATEST_VERSION = "1.0.0";
9576
+ var MIGRATION_PATH = ["0.3", "1.0.0"];
9220
9577
  function getVersionPolicy(version) {
9221
9578
  const policy = VERSION_MANIFEST[version];
9222
9579
  if (!policy) {
@@ -9224,635 +9581,673 @@ function getVersionPolicy(version) {
9224
9581
  }
9225
9582
  return policy;
9226
9583
  }
9584
+ function isVersionAtLeast(current, minimum) {
9585
+ const currentIdx = MIGRATION_PATH.indexOf(current);
9586
+ const minimumIdx = MIGRATION_PATH.indexOf(minimum);
9587
+ const effectiveCurrent = currentIdx === -1 ? MIGRATION_PATH.length : currentIdx;
9588
+ return effectiveCurrent >= minimumIdx;
9589
+ }
9227
9590
 
9228
- // src/core/lib/flowEngine/authorization.ts
9229
- var isAuthorized = async (blockId, actorDid, ucanService, flowUri, schemaVersion) => {
9230
- const policy = schemaVersion ? getVersionPolicy(schemaVersion) : null;
9231
- if (policy && !policy.ucanRequired) {
9232
- return { authorized: true };
9591
+ // src/core/lib/flowEngine/reconcile.ts
9592
+ var _reconcileRunning = false;
9593
+ function reconcilePendingInvocations(editor) {
9594
+ if (_reconcileRunning) return;
9595
+ _reconcileRunning = true;
9596
+ try {
9597
+ _reconcilePendingInvocationsInner(editor);
9598
+ } finally {
9599
+ _reconcileRunning = false;
9233
9600
  }
9234
- if (!ucanService) {
9235
- if (!policy) {
9236
- return { authorized: true };
9601
+ }
9602
+ function _reconcilePendingInvocationsInner(editor) {
9603
+ const yDoc = editor._yDoc;
9604
+ if (!yDoc) return;
9605
+ const blocks = editor.document || [];
9606
+ if (blocks.length === 0) return;
9607
+ const listenersBySource = /* @__PURE__ */ new Map();
9608
+ const barrierListenersBySource = /* @__PURE__ */ new Map();
9609
+ for (const block of blocks) {
9610
+ const trigger = parseTrigger(block);
9611
+ if (!trigger) continue;
9612
+ if (trigger.type === "block.event") {
9613
+ if (!trigger.sourceBlockId || !trigger.eventName) continue;
9614
+ const key = `${trigger.sourceBlockId}::${trigger.eventName}`;
9615
+ if (!listenersBySource.has(key)) listenersBySource.set(key, []);
9616
+ listenersBySource.get(key).push({ block, trigger });
9617
+ } else if (trigger.type === "block.event.all" && trigger.sources) {
9618
+ for (const source of trigger.sources) {
9619
+ if (!source.sourceBlockId || !source.eventName) continue;
9620
+ const key = `${source.sourceBlockId}::${source.eventName}`;
9621
+ if (!barrierListenersBySource.has(key)) barrierListenersBySource.set(key, []);
9622
+ barrierListenersBySource.get(key).push({ block, trigger, source });
9623
+ }
9237
9624
  }
9238
- return {
9239
- authorized: false,
9240
- reason: "UCAN service is not configured. This flow version requires UCAN authorization."
9241
- };
9242
- }
9243
- const capability = {
9244
- can: "flow/block/execute",
9245
- with: `${flowUri}:${blockId}`
9246
- };
9247
- const result = await ucanService.validateDelegationChain(actorDid, capability);
9248
- if (!result.valid) {
9249
- return {
9250
- authorized: false,
9251
- reason: result.error || "No valid capability chain found"
9252
- };
9253
9625
  }
9254
- const proofCids = result.proofChain?.map((d) => d.cid) || [];
9255
- return {
9256
- authorized: true,
9257
- capabilityId: proofCids[0],
9258
- proofCids
9259
- };
9260
- };
9261
-
9262
- // src/core/lib/flowEngine/executor.ts
9263
- var updateRuntimeAfterSuccess = (node, actorDid, runtime, actionResult, invocationCid, now) => {
9264
- const updates = {
9265
- submittedByDid: actionResult.submittedByDid || actorDid,
9266
- evaluationStatus: actionResult.evaluationStatus || "pending",
9267
- executionTimestamp: now ? now() : Date.now(),
9268
- lastInvocationCid: invocationCid
9626
+ if (listenersBySource.size === 0 && barrierListenersBySource.size === 0) return;
9627
+ const now = Date.now();
9628
+ sweepExpiredPendingInvocations(yDoc, now);
9629
+ const consumedByListener = /* @__PURE__ */ new Map();
9630
+ const isConsumed = (listenerBlockId, pendingId) => {
9631
+ let consumed = consumedByListener.get(listenerBlockId);
9632
+ if (!consumed) {
9633
+ consumed = readConsumedPendingInvocationIds(yDoc, listenerBlockId);
9634
+ consumedByListener.set(listenerBlockId, consumed);
9635
+ }
9636
+ return consumed.has(pendingId);
9637
+ };
9638
+ const schemaVersionRaw = yDoc.getMap("root").get("schema_version");
9639
+ const ucanRequired = typeof schemaVersionRaw === "string" ? !!getVersionPolicy(schemaVersionRaw)?.ucanRequired : false;
9640
+ const invocationsMap = yDoc.getMap("invocations");
9641
+ const runtimeMap = editor._yRuntime;
9642
+ const getNodeOutput2 = (nodeId) => {
9643
+ if (!runtimeMap) return void 0;
9644
+ const state = runtimeMap.get(nodeId);
9645
+ if (!state || typeof state !== "object") return void 0;
9646
+ const out = state.output;
9647
+ return out && typeof out === "object" ? out : void 0;
9269
9648
  };
9270
- if (actionResult.claimId) {
9271
- updates.claimId = actionResult.claimId;
9649
+ for (const block of blocks) {
9650
+ const sourceBlockId = block?.id;
9651
+ if (!sourceBlockId) continue;
9652
+ let hasAnyListener = false;
9653
+ for (const key of listenersBySource.keys()) {
9654
+ if (key.startsWith(`${sourceBlockId}::`)) {
9655
+ hasAnyListener = true;
9656
+ break;
9657
+ }
9658
+ }
9659
+ for (const key of barrierListenersBySource.keys()) {
9660
+ if (key.startsWith(`${sourceBlockId}::`)) {
9661
+ hasAnyListener = true;
9662
+ break;
9663
+ }
9664
+ }
9665
+ if (!hasAnyListener) continue;
9666
+ const records = readRunRecords(yDoc, sourceBlockId);
9667
+ for (const record of records) {
9668
+ if (!isTrustedRunRecord(record, ucanRequired, invocationsMap)) continue;
9669
+ processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNodeOutput2, runtimeMap, isConsumed, now);
9670
+ processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap, isConsumed, now);
9671
+ }
9272
9672
  }
9273
- runtime.update(node.id, updates);
9274
- };
9275
- var executeNode = async ({ node, actorDid, actorType, entityRoomId, context, action, pin }) => {
9276
- const { runtime, ucanService, invocationStore, flowUri, flowId, schemaVersion, now } = context;
9277
- const auth = await isAuthorized(node.id, actorDid, ucanService, flowUri, schemaVersion);
9278
- if (!auth.authorized) {
9279
- return { success: false, stage: "authorization", error: auth.reason };
9280
- }
9281
- if (node.linkedClaim && !node.linkedClaim.collectionId) {
9282
- return { success: false, stage: "claim", error: "Linked claim collection is required but missing." };
9283
- }
9284
- let invocationCid;
9285
- let invocationData;
9286
- if (ucanService && auth.proofCids && auth.proofCids.length > 0) {
9287
- const capability = {
9288
- can: "flow/block/execute",
9289
- with: `${flowUri}:${node.id}`
9290
- };
9291
- try {
9292
- const invocationResult = await ucanService.createAndValidateInvocation(
9293
- {
9294
- invokerDid: actorDid,
9295
- invokerType: actorType,
9296
- entityRoomId,
9297
- capability,
9298
- proofs: auth.proofCids,
9299
- pin
9300
- },
9301
- flowId,
9302
- node.id
9303
- );
9304
- if (!invocationResult.valid) {
9305
- return {
9306
- success: false,
9307
- stage: "authorization",
9308
- error: `Invocation validation failed: ${invocationResult.error}`
9309
- };
9310
- }
9311
- invocationCid = invocationResult.cid;
9312
- invocationData = invocationResult.invocation;
9313
- } catch (error) {
9314
- const message = error instanceof Error ? error.message : "Failed to create invocation";
9315
- return { success: false, stage: "authorization", error: message };
9316
- }
9317
- }
9318
- try {
9319
- const result = await action();
9320
- if (node.linkedClaim && !result.claimId) {
9321
- if (invocationStore && invocationCid && invocationData) {
9322
- const storedInvocation = {
9323
- cid: invocationCid,
9324
- invocation: invocationData,
9325
- invokerDid: actorDid,
9326
- capability: { can: "flow/block/execute", with: `${flowUri}:${node.id}` },
9327
- executedAt: now ? now() : Date.now(),
9328
- flowId,
9329
- blockId: node.id,
9330
- result: "failure",
9331
- error: "Execution did not return a claimId for linked claim requirement.",
9332
- proofCids: auth.proofCids || []
9333
- };
9334
- invocationStore.add(storedInvocation);
9335
- }
9336
- return {
9337
- success: false,
9338
- stage: "claim",
9339
- error: "Execution did not return a claimId for linked claim requirement.",
9340
- invocationCid
9673
+ }
9674
+ function isTrustedRunRecord(record, ucanRequired, invocationsMap) {
9675
+ if (!ucanRequired) return true;
9676
+ if (!record.invocationCid) return false;
9677
+ return invocationsMap.get(record.invocationCid) !== void 0;
9678
+ }
9679
+ function processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNodeOutput2, runtimeMap, isConsumed, now) {
9680
+ if (!Array.isArray(record.events)) return;
9681
+ record.events.forEach((event, eventIndex) => {
9682
+ if (!event?.name) return;
9683
+ const key = `${sourceBlockId}::${event.name}`;
9684
+ const listeners = listenersBySource.get(key);
9685
+ if (!listeners || listeners.length === 0) return;
9686
+ for (const { block: listenerBlock } of listeners) {
9687
+ const listenerBlockId = listenerBlock.id;
9688
+ if (!listenerBlockId) continue;
9689
+ const id = computePendingInvocationId({
9690
+ sourceBlockId,
9691
+ sourceRunId: record.runId,
9692
+ listenerBlockId,
9693
+ eventName: event.name,
9694
+ eventIndex
9695
+ });
9696
+ if (isConsumed(listenerBlockId, id)) continue;
9697
+ const assigneeDid = resolveAssignee(listenerBlock) || "unassigned";
9698
+ const inputs = parseInputs(listenerBlock);
9699
+ const refSnapshots = snapshotInputRefs(inputs, getNodeOutput2);
9700
+ const expiresAt = computeExpiry(listenerBlock, record.completedAt);
9701
+ if (expiresAt && Date.parse(expiresAt) < now) continue;
9702
+ const invocation = {
9703
+ id,
9704
+ triggeringBlockId: sourceBlockId,
9705
+ sourceRunId: record.runId,
9706
+ eventName: event.name,
9707
+ eventIndex,
9708
+ payload: event.payload,
9709
+ refSnapshots,
9710
+ assigneeDid,
9711
+ emittedAt: record.completedAt,
9712
+ expiresAt
9341
9713
  };
9714
+ queuePendingInvocation(yDoc, listenerBlockId, invocation);
9715
+ if (runtimeMap) {
9716
+ const prev = runtimeMap.get(listenerBlockId) || {};
9717
+ runtimeMap.set(listenerBlockId, {
9718
+ ...prev,
9719
+ pendingPayload: { ...event.payload, ...refSnapshots }
9720
+ });
9721
+ }
9342
9722
  }
9343
- if (invocationStore && invocationCid && invocationData) {
9344
- const storedInvocation = {
9345
- cid: invocationCid,
9346
- invocation: invocationData,
9347
- invokerDid: actorDid,
9348
- capability: { can: "flow/block/execute", with: `${flowUri}:${node.id}` },
9349
- executedAt: now ? now() : Date.now(),
9350
- flowId,
9351
- blockId: node.id,
9352
- result: "success",
9353
- proofCids: auth.proofCids || [],
9354
- claimId: result.claimId
9723
+ });
9724
+ }
9725
+ function processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap, isConsumed, now) {
9726
+ if (!Array.isArray(record.events)) return;
9727
+ for (const event of record.events) {
9728
+ if (!event?.name) continue;
9729
+ const key = `${sourceBlockId}::${event.name}`;
9730
+ const listeners = barrierListenersBySource.get(key);
9731
+ if (!listeners || listeners.length === 0) continue;
9732
+ for (const { block: listenerBlock, trigger, source } of listeners) {
9733
+ const listenerBlockId = listenerBlock.id;
9734
+ if (!listenerBlockId) continue;
9735
+ const entry = {
9736
+ sourceBlockId,
9737
+ eventName: event.name,
9738
+ alias: source.alias,
9739
+ runId: record.runId,
9740
+ payload: event.payload || {},
9741
+ emittedAt: record.completedAt
9355
9742
  };
9356
- invocationStore.add(storedInvocation);
9357
- }
9358
- updateRuntimeAfterSuccess(node, actorDid, runtime, result, invocationCid || auth.capabilityId, now);
9359
- return {
9360
- success: true,
9361
- stage: "complete",
9362
- result,
9363
- capabilityId: auth.capabilityId,
9364
- invocationCid
9365
- };
9366
- } catch (error) {
9367
- const message = error instanceof Error ? error.message : "Execution failed";
9368
- if (invocationStore && invocationCid && invocationData) {
9369
- const storedInvocation = {
9370
- cid: invocationCid,
9371
- invocation: invocationData,
9372
- invokerDid: actorDid,
9373
- capability: { can: "flow/block/execute", with: `${flowUri}:${node.id}` },
9374
- executedAt: now ? now() : Date.now(),
9375
- flowId,
9376
- blockId: node.id,
9377
- result: "failure",
9378
- error: message,
9379
- proofCids: auth.proofCids || []
9743
+ recordBarrierEvent(yDoc, listenerBlockId, entry);
9744
+ if (runtimeMap) {
9745
+ const prev = runtimeMap.get(listenerBlockId) || {};
9746
+ const existing = prev.pendingPayload || {};
9747
+ runtimeMap.set(listenerBlockId, {
9748
+ ...prev,
9749
+ pendingPayload: { ...existing, ...event.payload || {} }
9750
+ });
9751
+ }
9752
+ const allSources = trigger.sources || [];
9753
+ const currentState = readBarrierState(yDoc, listenerBlockId);
9754
+ const requiredSources = allSources.filter((s) => s.optional !== true);
9755
+ const gatingSources = requiredSources.length > 0 ? requiredSources : allSources;
9756
+ const allFired = gatingSources.length > 0 && gatingSources.every((s) => currentState.some((e) => e.sourceBlockId === s.sourceBlockId && e.eventName === s.eventName));
9757
+ if (!allFired) continue;
9758
+ const assigneeDid = resolveAssignee(listenerBlock) || "unassigned";
9759
+ const id = computeBarrierInvocationId(currentState, listenerBlockId);
9760
+ if (isConsumed(listenerBlockId, id)) continue;
9761
+ const mergedPayload = mergeBarrierPayloads(currentState);
9762
+ const inputs = parseInputs(listenerBlock);
9763
+ const refSnapshots = snapshotInputRefs(inputs, getNodeOutput2);
9764
+ const expiresAt = computeExpiry(listenerBlock, record.completedAt);
9765
+ if (expiresAt && Date.parse(expiresAt) < now) continue;
9766
+ const invocation = {
9767
+ id,
9768
+ triggeringBlockId: sourceBlockId,
9769
+ sourceRunId: record.runId,
9770
+ eventName: `barrier:${allSources.map((s) => s.alias).join("+")}`,
9771
+ eventIndex: 0,
9772
+ payload: mergedPayload,
9773
+ refSnapshots,
9774
+ assigneeDid,
9775
+ emittedAt: record.completedAt,
9776
+ expiresAt
9380
9777
  };
9381
- invocationStore.add(storedInvocation);
9778
+ queuePendingInvocation(yDoc, listenerBlockId, invocation);
9779
+ clearBarrierState(yDoc, listenerBlockId);
9780
+ if (runtimeMap) {
9781
+ const prev = runtimeMap.get(listenerBlockId) || {};
9782
+ runtimeMap.set(listenerBlockId, {
9783
+ ...prev,
9784
+ pendingPayload: { ...mergedPayload, ...refSnapshots }
9785
+ });
9786
+ }
9382
9787
  }
9383
- return { success: false, stage: "action", error: message, invocationCid };
9384
9788
  }
9385
- };
9386
-
9387
- // src/core/lib/flowEngine/triggers.ts
9388
- import * as Y from "yjs";
9389
- var RUN_RECORD_AUDIT_TYPE = "block.run";
9390
- function computePendingInvocationId(args) {
9391
- const { sourceBlockId, sourceRunId, listenerBlockId, eventName, eventIndex } = args;
9392
- const input = `${sourceBlockId}:${sourceRunId}:${listenerBlockId}:${eventName}:${eventIndex}`;
9393
- return `pi-${fnv1a32(input)}`;
9394
- }
9395
- function snapshotInputRefs(inputs, getNodeOutput2) {
9396
- const snapshots = {};
9397
- walkRefs(inputs, (ref) => {
9398
- if (ref.$ref.startsWith("trigger.")) return;
9399
- const parsed = parseOutputRef(ref.$ref);
9400
- if (!parsed) return;
9401
- const output = getNodeOutput2(parsed.nodeId);
9402
- if (!output) return;
9403
- snapshots[ref.$ref] = getNestedValue3(output, parsed.fieldPath);
9404
- });
9405
- return snapshots;
9406
- }
9407
- function walkRefs(value, visit) {
9408
- if (isRuntimeRef(value)) {
9409
- visit(value);
9410
- return;
9411
- }
9412
- if (Array.isArray(value)) {
9413
- for (const item of value) walkRefs(item, visit);
9414
- return;
9415
- }
9416
- if (typeof value === "object" && value !== null) {
9417
- for (const v of Object.values(value)) walkRefs(v, visit);
9418
- }
9419
- }
9420
- function parseOutputRef(ref) {
9421
- const outputIndex = ref.indexOf(".output.");
9422
- if (outputIndex === -1) return null;
9423
- return {
9424
- nodeId: ref.slice(0, outputIndex),
9425
- fieldPath: ref.slice(outputIndex + ".output.".length)
9426
- };
9427
- }
9428
- function getNestedValue3(obj, path) {
9429
- const parts = path.split(".");
9430
- let current = obj;
9431
- for (const part of parts) {
9432
- if (current == null || typeof current !== "object") return void 0;
9433
- current = current[part];
9434
- }
9435
- return current;
9436
9789
  }
9437
- function fnv1a32(input) {
9438
- let hash = 2166136261;
9439
- for (let i = 0; i < input.length; i++) {
9440
- hash ^= input.charCodeAt(i);
9441
- hash = Math.imul(hash, 16777619);
9790
+ function parseTrigger(block) {
9791
+ const raw = block?.props?.trigger;
9792
+ if (!raw || typeof raw !== "string") return null;
9793
+ try {
9794
+ const parsed = JSON.parse(raw);
9795
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
9796
+ return parsed;
9797
+ }
9798
+ } catch {
9799
+ warnOnce(`trigger-parse:${block?.id}`, `[flow-config] block ${block?.id}: props.trigger is not valid JSON; trigger ignored`);
9442
9800
  }
9443
- return (hash >>> 0).toString(16).padStart(8, "0");
9801
+ return null;
9444
9802
  }
9445
- var PENDING_INVOCATIONS_MAP_KEY = "pendingInvocations";
9446
- function getPendingInvocationsMap(yDoc) {
9447
- return yDoc.getMap(PENDING_INVOCATIONS_MAP_KEY);
9803
+ function parseInputs(block) {
9804
+ const raw = block?.props?.inputs;
9805
+ if (!raw || typeof raw !== "string") return {};
9806
+ try {
9807
+ const parsed = JSON.parse(raw);
9808
+ if (parsed && typeof parsed === "object") return parsed;
9809
+ } catch {
9810
+ warnOnce(`inputs-parse:${block?.id}`, `[flow-config] block ${block?.id}: props.inputs is not valid JSON; treated as empty`);
9811
+ }
9812
+ return {};
9448
9813
  }
9449
- function getOrCreateBlockPendingMap(yDoc, blockId) {
9450
- const outer = getPendingInvocationsMap(yDoc);
9451
- let inner = outer.get(blockId);
9452
- if (!inner) {
9453
- inner = new Y.Map();
9454
- outer.set(blockId, inner);
9814
+ function resolveAssignee(block) {
9815
+ const raw = block?.props?.assignment;
9816
+ if (!raw) return void 0;
9817
+ try {
9818
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
9819
+ const did = parsed?.assignedActor?.did;
9820
+ return typeof did === "string" && did.length > 0 ? did : void 0;
9821
+ } catch {
9822
+ warnOnce(`assignment-parse:${block?.id}`, `[flow-config] block ${block?.id}: props.assignment is not valid JSON; assignee unresolved`);
9823
+ return void 0;
9455
9824
  }
9456
- return inner;
9457
9825
  }
9458
- function readPendingInvocations(yDoc, blockId) {
9459
- const outer = getPendingInvocationsMap(yDoc);
9460
- const inner = outer.get(blockId);
9461
- if (!inner) return [];
9462
- const items = [];
9463
- inner.forEach((value) => {
9464
- if (value && typeof value === "object") {
9465
- items.push(value);
9826
+ function computeExpiry(block, emittedAt) {
9827
+ const ttlAbsolute = block?.props?.ttlAbsoluteDueDate;
9828
+ if (typeof ttlAbsolute === "string" && ttlAbsolute) {
9829
+ return ttlAbsolute;
9830
+ }
9831
+ const ttlFromEnablement = block?.props?.ttlFromEnablement;
9832
+ if (typeof ttlFromEnablement === "string" && ttlFromEnablement) {
9833
+ const ms = parseIsoDurationToMs(ttlFromEnablement);
9834
+ if (ms != null) {
9835
+ return new Date(new Date(emittedAt).getTime() + ms).toISOString();
9466
9836
  }
9467
- });
9468
- items.sort((a, b) => a.emittedAt.localeCompare(b.emittedAt));
9469
- return items;
9837
+ }
9838
+ return new Date(new Date(emittedAt).getTime() + 7 * 24 * 60 * 60 * 1e3).toISOString();
9470
9839
  }
9471
- function queuePendingInvocation(yDoc, listenerBlockId, invocation) {
9472
- let created = false;
9473
- yDoc.transact(() => {
9474
- const inner = getOrCreateBlockPendingMap(yDoc, listenerBlockId);
9475
- if (inner.has(invocation.id)) return;
9476
- inner.set(invocation.id, invocation);
9477
- created = true;
9478
- });
9479
- return created;
9840
+ function parseIsoDurationToMs(duration) {
9841
+ const match = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/.exec(duration);
9842
+ if (!match) return null;
9843
+ const [, d, h, m, s] = match;
9844
+ let ms = 0;
9845
+ if (d) ms += parseInt(d, 10) * 24 * 60 * 60 * 1e3;
9846
+ if (h) ms += parseInt(h, 10) * 60 * 60 * 1e3;
9847
+ if (m) ms += parseInt(m, 10) * 60 * 1e3;
9848
+ if (s) ms += parseInt(s, 10) * 1e3;
9849
+ return ms;
9480
9850
  }
9481
- function removePendingInvocation(yDoc, listenerBlockId, pendingInvocationId) {
9482
- const outer = getPendingInvocationsMap(yDoc);
9483
- const inner = outer.get(listenerBlockId);
9484
- if (!inner) return false;
9485
- if (!inner.has(pendingInvocationId)) return false;
9486
- inner.delete(pendingInvocationId);
9487
- return true;
9851
+ function getActionForBlock(block) {
9852
+ const actionType = block?.props?.actionType;
9853
+ if (typeof actionType !== "string") return void 0;
9854
+ return getAction(actionType);
9488
9855
  }
9489
- var BARRIER_STATE_MAP_KEY = "barrierState";
9490
- function getBarrierStateMap(yDoc) {
9491
- return yDoc.getMap(BARRIER_STATE_MAP_KEY);
9856
+
9857
+ // src/core/lib/flowEngine/emitEvents.ts
9858
+ function writeRunRecordAndReconcile(editor, blockId, output, events, actorDid, detailsPatch = {}) {
9859
+ const yDoc = editor._yDoc;
9860
+ if (!yDoc) return;
9861
+ if (events.length === 0) return;
9862
+ const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
9863
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9864
+ const details = {
9865
+ runId,
9866
+ output,
9867
+ events,
9868
+ startedAt: now,
9869
+ completedAt: now,
9870
+ actorDid,
9871
+ ...detailsPatch
9872
+ };
9873
+ appendRunRecord(yDoc, blockId, details, actorDid);
9874
+ reconcilePendingInvocations(editor);
9492
9875
  }
9493
- function getOrCreateListenerBarrierMap(yDoc, listenerBlockId) {
9494
- const outer = getBarrierStateMap(yDoc);
9495
- let inner = outer.get(listenerBlockId);
9496
- if (!inner) {
9497
- inner = new Y.Map();
9498
- outer.set(listenerBlockId, inner);
9876
+
9877
+ // src/core/lib/flowEngine/utils.ts
9878
+ var buildAuthzFromProps = (props) => {
9879
+ const linkedClaimCollectionId = typeof props.linkedClaimCollectionId === "string" ? props.linkedClaimCollectionId.trim() : "";
9880
+ const authz = {};
9881
+ if (linkedClaimCollectionId) {
9882
+ authz.linkedClaim = { collectionId: linkedClaimCollectionId };
9499
9883
  }
9500
- return inner;
9501
- }
9502
- function recordBarrierEvent(yDoc, listenerBlockId, entry) {
9503
- let written = false;
9504
- yDoc.transact(() => {
9505
- const inner = getOrCreateListenerBarrierMap(yDoc, listenerBlockId);
9506
- const key = `${entry.sourceBlockId}::${entry.eventName}`;
9507
- const existing = inner.get(key);
9508
- if (existing && typeof existing === "object" && existing.runId === entry.runId) return;
9509
- inner.set(key, entry);
9510
- written = true;
9511
- });
9512
- return written;
9513
- }
9514
- function readBarrierState(yDoc, listenerBlockId) {
9515
- const outer = getBarrierStateMap(yDoc);
9516
- const inner = outer.get(listenerBlockId);
9517
- if (!inner) return [];
9518
- const entries = [];
9519
- inner.forEach((value) => {
9520
- if (value && typeof value === "object") {
9521
- entries.push(value);
9884
+ return authz;
9885
+ };
9886
+ var buildFlowNodeFromBlock = (block) => {
9887
+ const base = {
9888
+ id: block.id,
9889
+ type: block.type,
9890
+ props: block.props || {}
9891
+ };
9892
+ const authz = buildAuthzFromProps(block.props || {});
9893
+ return {
9894
+ ...base,
9895
+ ...authz
9896
+ };
9897
+ };
9898
+
9899
+ // src/core/lib/flowEngine/runtime.ts
9900
+ var XERO_WORK_ITEMS_MAP_NAME = "xeroWorkItems";
9901
+ var XERO_CONNECTION_MAP_NAME = "xeroConnection";
9902
+ var ensureStateObject = (value) => {
9903
+ if (!value || typeof value !== "object") {
9904
+ return {};
9905
+ }
9906
+ return { ...value };
9907
+ };
9908
+ var createYMapManager = (map) => {
9909
+ return {
9910
+ get: (nodeId) => {
9911
+ const stored = map.get(nodeId);
9912
+ return ensureStateObject(stored);
9913
+ },
9914
+ update: (nodeId, updates) => {
9915
+ const current = ensureStateObject(map.get(nodeId));
9916
+ map.set(nodeId, { ...current, ...updates });
9522
9917
  }
9523
- });
9524
- return entries;
9525
- }
9526
- function clearBarrierState(yDoc, listenerBlockId) {
9527
- const outer = getBarrierStateMap(yDoc);
9528
- yDoc.transact(() => {
9529
- outer.delete(listenerBlockId);
9530
- });
9531
- }
9532
- function computeBarrierInvocationId(entries, listenerBlockId) {
9533
- const sorted = [...entries].sort((a, b) => a.sourceBlockId.localeCompare(b.sourceBlockId));
9534
- const input = sorted.map((e) => `${e.sourceBlockId}:${e.runId}:${e.eventName}`).join("|") + `|>${listenerBlockId}`;
9535
- return `bi-${fnv1a32(input)}`;
9536
- }
9537
- function mergeBarrierPayloads(entries) {
9538
- const merged = {};
9539
- for (const entry of entries) {
9540
- if (entry.alias) {
9541
- merged[entry.alias] = entry.payload;
9542
- } else {
9543
- Object.assign(merged, entry.payload);
9918
+ };
9919
+ };
9920
+ var createMemoryManager = () => {
9921
+ const memory = /* @__PURE__ */ new Map();
9922
+ return {
9923
+ get: (nodeId) => ensureStateObject(memory.get(nodeId)),
9924
+ update: (nodeId, updates) => {
9925
+ const current = ensureStateObject(memory.get(nodeId));
9926
+ memory.set(nodeId, { ...current, ...updates });
9544
9927
  }
9928
+ };
9929
+ };
9930
+ var createRuntimeStateManager = (editor) => {
9931
+ if (editor?._yRuntime) {
9932
+ return createYMapManager(editor._yRuntime);
9545
9933
  }
9546
- return merged;
9547
- }
9548
- function appendRunRecord(yDoc, blockId, details, userId) {
9549
- const auditMap = yDoc.getMap("auditTrail");
9934
+ return createMemoryManager();
9935
+ };
9936
+ var createYDocRuntimeManager = (yDoc) => {
9937
+ return createYMapManager(yDoc.getMap("runtime"));
9938
+ };
9939
+ function clearRuntimeForTemplateClone(yDoc) {
9940
+ const runtime = yDoc.getMap("runtime");
9941
+ const invocations = yDoc.getMap("invocations");
9942
+ const pendingInvocations = yDoc.getMap("pendingInvocations");
9943
+ const agentOutbox = yDoc.getMap("agentOutbox");
9944
+ const agentLeases = yDoc.getMap("agentLeases");
9945
+ const auditTrail = yDoc.getMap("auditTrail");
9946
+ const xeroWorkItems = yDoc.getMap(XERO_WORK_ITEMS_MAP_NAME);
9947
+ const xeroConnection = yDoc.getMap(XERO_CONNECTION_MAP_NAME);
9550
9948
  yDoc.transact(() => {
9551
- let arr = auditMap.get(blockId);
9552
- if (!arr) {
9553
- arr = new Y.Array();
9554
- auditMap.set(blockId, arr);
9555
- }
9556
- const event = {
9557
- id: `${details.runId}`,
9558
- blockId,
9559
- type: RUN_RECORD_AUDIT_TYPE,
9560
- details,
9561
- message: void 0,
9562
- meta: {
9563
- timestamp: details.completedAt,
9564
- userId,
9565
- editable: false
9566
- }
9567
- };
9568
- arr.push([event]);
9949
+ runtime.forEach((_, key) => runtime.delete(key));
9950
+ invocations.forEach((_, key) => invocations.delete(key));
9951
+ pendingInvocations.forEach((_, key) => pendingInvocations.delete(key));
9952
+ agentOutbox.forEach((_, key) => agentOutbox.delete(key));
9953
+ agentLeases.forEach((_, key) => agentLeases.delete(key));
9954
+ auditTrail.forEach((_, key) => auditTrail.delete(key));
9955
+ xeroWorkItems.forEach((_, key) => xeroWorkItems.delete(key));
9956
+ xeroConnection.forEach((_, key) => xeroConnection.delete(key));
9569
9957
  });
9570
9958
  }
9571
- function readRunRecords(yDoc, blockId) {
9572
- const auditMap = yDoc.getMap("auditTrail");
9573
- const arr = auditMap.get(blockId);
9574
- if (!arr) return [];
9575
- const records = [];
9576
- arr.forEach((entry) => {
9577
- if (!entry || typeof entry !== "object") return;
9578
- const e = entry;
9579
- if (e.type === RUN_RECORD_AUDIT_TYPE && e.details) {
9580
- records.push(e.details);
9581
- }
9582
- });
9583
- return records;
9959
+
9960
+ // src/core/lib/flowCompiler/resolveRefs.ts
9961
+ function resolveRuntimeRefs(nb, getNodeOutput2, triggerContext) {
9962
+ return resolveValue(nb, getNodeOutput2, triggerContext);
9584
9963
  }
9585
- function findFailedListenersForSourceRun(yDoc, sourceBlockId, sourceRunId, listenerBlockIds) {
9586
- const failures = [];
9587
- for (const listenerBlockId of listenerBlockIds) {
9588
- const records = readRunRecords(yDoc, listenerBlockId);
9589
- for (const record of records) {
9590
- if (!record.error) continue;
9591
- if (record.triggeredBy?.sourceBlockId !== sourceBlockId) continue;
9592
- if (record.fromPendingInvocationId == null) continue;
9593
- const sourceRunIdField = record.sourceRunId;
9594
- if (sourceRunIdField && sourceRunIdField !== sourceRunId) continue;
9595
- failures.push({ listenerBlockId, record });
9964
+ function resolveValue(value, getNodeOutput2, triggerContext) {
9965
+ if (isRuntimeRef(value)) {
9966
+ return resolveRef(value.$ref, getNodeOutput2, triggerContext);
9967
+ }
9968
+ if (Array.isArray(value)) {
9969
+ return value.map((item) => resolveValue(item, getNodeOutput2, triggerContext));
9970
+ }
9971
+ if (typeof value === "object" && value !== null) {
9972
+ const result = {};
9973
+ for (const [key, val] of Object.entries(value)) {
9974
+ result[key] = resolveValue(val, getNodeOutput2, triggerContext);
9596
9975
  }
9976
+ return result;
9597
9977
  }
9598
- return failures;
9978
+ return value;
9599
9979
  }
9600
- function replayFailedListenerRun(yDoc, failedRecord, listenerBlockId, originalPayload, originalRefSnapshots, assigneeDid) {
9601
- if (!failedRecord.triggeredBy) return false;
9602
- const replayId = `${failedRecord.runId}:replay-${Date.now().toString(36)}`;
9603
- const now = (/* @__PURE__ */ new Date()).toISOString();
9604
- const replay = {
9605
- id: replayId,
9606
- triggeringBlockId: failedRecord.triggeredBy.sourceBlockId,
9607
- sourceRunId: failedRecord.sourceRunId || failedRecord.runId,
9608
- eventName: failedRecord.triggeredBy.eventName,
9609
- eventIndex: 0,
9610
- payload: originalPayload,
9611
- refSnapshots: originalRefSnapshots,
9612
- assigneeDid,
9613
- emittedAt: now,
9614
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString()
9615
- };
9616
- return queuePendingInvocation(yDoc, listenerBlockId, replay);
9980
+ function resolveRef(ref, getNodeOutput2, triggerContext) {
9981
+ if (ref.startsWith("trigger.payload.")) {
9982
+ if (!triggerContext) {
9983
+ throw new Error(`Trigger ref "${ref}" used outside of a listener invocation context. trigger.payload.* refs are only valid on block.event-triggered blocks.`);
9984
+ }
9985
+ const fieldPath2 = ref.slice("trigger.payload.".length);
9986
+ return getNestedValue(triggerContext.payload, fieldPath2);
9987
+ }
9988
+ if (triggerContext && Object.prototype.hasOwnProperty.call(triggerContext.refSnapshots, ref)) {
9989
+ return triggerContext.refSnapshots[ref];
9990
+ }
9991
+ const outputIndex = ref.indexOf(".output.");
9992
+ if (outputIndex === -1) {
9993
+ throw new Error(`Invalid runtime reference "${ref}". Expected format: "nodeId.output.fieldPath" or "trigger.payload.fieldPath"`);
9994
+ }
9995
+ const nodeId = ref.slice(0, outputIndex);
9996
+ const fieldPath = ref.slice(outputIndex + ".output.".length);
9997
+ const output = getNodeOutput2(nodeId);
9998
+ if (!output) {
9999
+ return void 0;
10000
+ }
10001
+ return getNestedValue(output, fieldPath);
9617
10002
  }
9618
10003
 
9619
- // src/core/lib/flowEngine/reconcile.ts
9620
- var _reconcileRunning = false;
9621
- function reconcilePendingInvocations(editor) {
9622
- if (_reconcileRunning) return;
9623
- _reconcileRunning = true;
9624
- try {
9625
- _reconcilePendingInvocationsInner(editor);
9626
- } finally {
9627
- _reconcileRunning = false;
10004
+ // src/core/lib/flowEngine/authorization.ts
10005
+ var isAuthorized = async (blockId, actorDid, ucanService, flowUri, schemaVersion) => {
10006
+ const policy = schemaVersion ? getVersionPolicy(schemaVersion) : null;
10007
+ if (policy && !policy.ucanRequired) {
10008
+ return { authorized: true };
9628
10009
  }
9629
- }
9630
- function _reconcilePendingInvocationsInner(editor) {
9631
- const yDoc = editor._yDoc;
9632
- if (!yDoc) return;
9633
- const blocks = editor.document || [];
9634
- if (blocks.length === 0) return;
9635
- const listenersBySource = /* @__PURE__ */ new Map();
9636
- const barrierListenersBySource = /* @__PURE__ */ new Map();
9637
- for (const block of blocks) {
9638
- const trigger = parseTrigger(block);
9639
- if (!trigger) continue;
9640
- if (trigger.type === "block.event") {
9641
- if (!trigger.sourceBlockId || !trigger.eventName) continue;
9642
- const key = `${trigger.sourceBlockId}::${trigger.eventName}`;
9643
- if (!listenersBySource.has(key)) listenersBySource.set(key, []);
9644
- listenersBySource.get(key).push({ block, trigger });
9645
- } else if (trigger.type === "block.event.all" && trigger.sources) {
9646
- for (const source of trigger.sources) {
9647
- if (!source.sourceBlockId || !source.eventName) continue;
9648
- const key = `${source.sourceBlockId}::${source.eventName}`;
9649
- if (!barrierListenersBySource.has(key)) barrierListenersBySource.set(key, []);
9650
- barrierListenersBySource.get(key).push({ block, trigger, source });
9651
- }
10010
+ if (!ucanService) {
10011
+ if (!policy) {
10012
+ return { authorized: true };
9652
10013
  }
10014
+ return {
10015
+ authorized: false,
10016
+ reason: "UCAN service is not configured. This flow version requires UCAN authorization."
10017
+ };
9653
10018
  }
9654
- if (listenersBySource.size === 0 && barrierListenersBySource.size === 0) return;
9655
- const runtimeMap = editor._yRuntime;
9656
- const getNodeOutput2 = (nodeId) => {
9657
- if (!runtimeMap) return void 0;
9658
- const state = runtimeMap.get(nodeId);
9659
- if (!state || typeof state !== "object") return void 0;
9660
- const out = state.output;
9661
- return out && typeof out === "object" ? out : void 0;
10019
+ const capability = {
10020
+ can: "flow/block/execute",
10021
+ with: `${flowUri}:${blockId}`
9662
10022
  };
9663
- for (const block of blocks) {
9664
- const sourceBlockId = block?.id;
9665
- if (!sourceBlockId) continue;
9666
- let hasAnyListener = false;
9667
- for (const key of listenersBySource.keys()) {
9668
- if (key.startsWith(`${sourceBlockId}::`)) {
9669
- hasAnyListener = true;
9670
- break;
9671
- }
9672
- }
9673
- for (const key of barrierListenersBySource.keys()) {
9674
- if (key.startsWith(`${sourceBlockId}::`)) {
9675
- hasAnyListener = true;
9676
- break;
10023
+ const result = await ucanService.validateDelegationChain(actorDid, capability);
10024
+ if (!result.valid) {
10025
+ return {
10026
+ authorized: false,
10027
+ reason: result.error || "No valid capability chain found"
10028
+ };
10029
+ }
10030
+ const proofCids = result.proofChain?.map((d) => d.cid) || [];
10031
+ return {
10032
+ authorized: true,
10033
+ capabilityId: proofCids[0],
10034
+ proofCids
10035
+ };
10036
+ };
10037
+
10038
+ // src/core/lib/flowEngine/executor.ts
10039
+ var updateRuntimeAfterSuccess = (node, actorDid, runtime, actionResult, invocationCid, now) => {
10040
+ const updates = {
10041
+ submittedByDid: actionResult.submittedByDid || actorDid,
10042
+ evaluationStatus: actionResult.evaluationStatus || "pending",
10043
+ executionTimestamp: now ? now() : Date.now(),
10044
+ lastInvocationCid: invocationCid
10045
+ };
10046
+ if (actionResult.claimId) {
10047
+ updates.claimId = actionResult.claimId;
10048
+ }
10049
+ runtime.update(node.id, updates);
10050
+ };
10051
+ var executeNode = async ({ node, actorDid, actorType, entityRoomId, context, action, pin }) => {
10052
+ const { runtime, ucanService, invocationStore, flowUri, flowId, schemaVersion, now } = context;
10053
+ const auth = await isAuthorized(node.id, actorDid, ucanService, flowUri, schemaVersion);
10054
+ if (!auth.authorized) {
10055
+ return { success: false, stage: "authorization", error: auth.reason };
10056
+ }
10057
+ if (node.linkedClaim && !node.linkedClaim.collectionId) {
10058
+ return { success: false, stage: "claim", error: "Linked claim collection is required but missing." };
10059
+ }
10060
+ let invocationCid;
10061
+ let invocationData;
10062
+ if (ucanService && auth.proofCids && auth.proofCids.length > 0) {
10063
+ const capability = {
10064
+ can: "flow/block/execute",
10065
+ with: `${flowUri}:${node.id}`
10066
+ };
10067
+ try {
10068
+ const invocationResult = await ucanService.createAndValidateInvocation(
10069
+ {
10070
+ invokerDid: actorDid,
10071
+ invokerType: actorType,
10072
+ entityRoomId,
10073
+ capability,
10074
+ proofs: auth.proofCids,
10075
+ pin
10076
+ },
10077
+ flowId,
10078
+ node.id
10079
+ );
10080
+ if (!invocationResult.valid) {
10081
+ return {
10082
+ success: false,
10083
+ stage: "authorization",
10084
+ error: `Invocation validation failed: ${invocationResult.error}`
10085
+ };
9677
10086
  }
9678
- }
9679
- if (!hasAnyListener) continue;
9680
- const records = readRunRecords(yDoc, sourceBlockId);
9681
- for (const record of records) {
9682
- processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNodeOutput2, runtimeMap);
9683
- processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap);
10087
+ invocationCid = invocationResult.cid;
10088
+ invocationData = invocationResult.invocation;
10089
+ } catch (error) {
10090
+ const message = error instanceof Error ? error.message : "Failed to create invocation";
10091
+ return { success: false, stage: "authorization", error: message };
9684
10092
  }
9685
10093
  }
9686
- }
9687
- function processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNodeOutput2, runtimeMap) {
9688
- if (!Array.isArray(record.events)) return;
9689
- record.events.forEach((event, eventIndex) => {
9690
- if (!event?.name) return;
9691
- const key = `${sourceBlockId}::${event.name}`;
9692
- const listeners = listenersBySource.get(key);
9693
- if (!listeners || listeners.length === 0) return;
9694
- for (const { block: listenerBlock } of listeners) {
9695
- const listenerBlockId = listenerBlock.id;
9696
- if (!listenerBlockId) continue;
9697
- const id = computePendingInvocationId({
9698
- sourceBlockId,
9699
- sourceRunId: record.runId,
9700
- listenerBlockId,
9701
- eventName: event.name,
9702
- eventIndex
9703
- });
9704
- const assigneeDid = resolveAssignee(listenerBlock) || "unassigned";
9705
- const inputs = parseInputs(listenerBlock);
9706
- const refSnapshots = snapshotInputRefs(inputs, getNodeOutput2);
9707
- const expiresAt = computeExpiry(listenerBlock, record.completedAt);
9708
- const invocation = {
9709
- id,
9710
- triggeringBlockId: sourceBlockId,
9711
- sourceRunId: record.runId,
9712
- eventName: event.name,
9713
- eventIndex,
9714
- payload: event.payload,
9715
- refSnapshots,
9716
- assigneeDid,
9717
- emittedAt: record.completedAt,
9718
- expiresAt
9719
- };
9720
- queuePendingInvocation(yDoc, listenerBlockId, invocation);
9721
- if (runtimeMap) {
9722
- const prev = runtimeMap.get(listenerBlockId) || {};
9723
- runtimeMap.set(listenerBlockId, {
9724
- ...prev,
9725
- pendingPayload: { ...event.payload, ...refSnapshots }
9726
- });
10094
+ try {
10095
+ const result = await action();
10096
+ if (node.linkedClaim && !result.claimId) {
10097
+ if (invocationStore && invocationCid && invocationData) {
10098
+ const storedInvocation = {
10099
+ cid: invocationCid,
10100
+ invocation: invocationData,
10101
+ invokerDid: actorDid,
10102
+ capability: { can: "flow/block/execute", with: `${flowUri}:${node.id}` },
10103
+ executedAt: now ? now() : Date.now(),
10104
+ flowId,
10105
+ blockId: node.id,
10106
+ result: "failure",
10107
+ error: "Execution did not return a claimId for linked claim requirement.",
10108
+ proofCids: auth.proofCids || []
10109
+ };
10110
+ invocationStore.add(storedInvocation);
9727
10111
  }
10112
+ return {
10113
+ success: false,
10114
+ stage: "claim",
10115
+ error: "Execution did not return a claimId for linked claim requirement.",
10116
+ invocationCid
10117
+ };
9728
10118
  }
9729
- });
9730
- }
9731
- function processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap) {
9732
- if (!Array.isArray(record.events)) return;
9733
- for (const event of record.events) {
9734
- if (!event?.name) continue;
9735
- const key = `${sourceBlockId}::${event.name}`;
9736
- const listeners = barrierListenersBySource.get(key);
9737
- if (!listeners || listeners.length === 0) continue;
9738
- for (const { block: listenerBlock, trigger, source } of listeners) {
9739
- const listenerBlockId = listenerBlock.id;
9740
- if (!listenerBlockId) continue;
9741
- const entry = {
9742
- sourceBlockId,
9743
- eventName: event.name,
9744
- alias: source.alias,
9745
- runId: record.runId,
9746
- payload: event.payload || {},
9747
- emittedAt: record.completedAt
10119
+ if (invocationStore && invocationCid && invocationData) {
10120
+ const storedInvocation = {
10121
+ cid: invocationCid,
10122
+ invocation: invocationData,
10123
+ invokerDid: actorDid,
10124
+ capability: { can: "flow/block/execute", with: `${flowUri}:${node.id}` },
10125
+ executedAt: now ? now() : Date.now(),
10126
+ flowId,
10127
+ blockId: node.id,
10128
+ result: "success",
10129
+ proofCids: auth.proofCids || [],
10130
+ claimId: result.claimId
9748
10131
  };
9749
- recordBarrierEvent(yDoc, listenerBlockId, entry);
9750
- if (runtimeMap) {
9751
- const prev = runtimeMap.get(listenerBlockId) || {};
9752
- const existing = prev.pendingPayload || {};
9753
- runtimeMap.set(listenerBlockId, {
9754
- ...prev,
9755
- pendingPayload: { ...existing, ...event.payload || {} }
9756
- });
9757
- }
9758
- const allSources = trigger.sources || [];
9759
- const currentState = readBarrierState(yDoc, listenerBlockId);
9760
- const requiredSources = allSources.filter((s) => s.optional !== true);
9761
- const gatingSources = requiredSources.length > 0 ? requiredSources : allSources;
9762
- const allFired = gatingSources.length > 0 && gatingSources.every((s) => currentState.some((e) => e.sourceBlockId === s.sourceBlockId && e.eventName === s.eventName));
9763
- if (!allFired) continue;
9764
- const assigneeDid = resolveAssignee(listenerBlock) || "unassigned";
9765
- const id = computeBarrierInvocationId(currentState, listenerBlockId);
9766
- const mergedPayload = mergeBarrierPayloads(currentState);
9767
- const inputs = parseInputs(listenerBlock);
9768
- const refSnapshots = snapshotInputRefs(inputs, getNodeOutput2);
9769
- const expiresAt = computeExpiry(listenerBlock, record.completedAt);
9770
- const invocation = {
9771
- id,
9772
- triggeringBlockId: sourceBlockId,
9773
- sourceRunId: record.runId,
9774
- eventName: `barrier:${allSources.map((s) => s.alias).join("+")}`,
9775
- eventIndex: 0,
9776
- payload: mergedPayload,
9777
- refSnapshots,
9778
- assigneeDid,
9779
- emittedAt: record.completedAt,
9780
- expiresAt
10132
+ invocationStore.add(storedInvocation);
10133
+ }
10134
+ updateRuntimeAfterSuccess(node, actorDid, runtime, result, invocationCid || auth.capabilityId, now);
10135
+ return {
10136
+ success: true,
10137
+ stage: "complete",
10138
+ result,
10139
+ capabilityId: auth.capabilityId,
10140
+ invocationCid
10141
+ };
10142
+ } catch (error) {
10143
+ const message = error instanceof Error ? error.message : "Execution failed";
10144
+ if (invocationStore && invocationCid && invocationData) {
10145
+ const storedInvocation = {
10146
+ cid: invocationCid,
10147
+ invocation: invocationData,
10148
+ invokerDid: actorDid,
10149
+ capability: { can: "flow/block/execute", with: `${flowUri}:${node.id}` },
10150
+ executedAt: now ? now() : Date.now(),
10151
+ flowId,
10152
+ blockId: node.id,
10153
+ result: "failure",
10154
+ error: message,
10155
+ proofCids: auth.proofCids || []
9781
10156
  };
9782
- queuePendingInvocation(yDoc, listenerBlockId, invocation);
9783
- clearBarrierState(yDoc, listenerBlockId);
9784
- if (runtimeMap) {
9785
- const prev = runtimeMap.get(listenerBlockId) || {};
9786
- runtimeMap.set(listenerBlockId, {
9787
- ...prev,
9788
- pendingPayload: { ...mergedPayload, ...refSnapshots }
9789
- });
9790
- }
10157
+ invocationStore.add(storedInvocation);
9791
10158
  }
10159
+ return { success: false, stage: "action", error: message, invocationCid };
9792
10160
  }
9793
- }
9794
- function parseTrigger(block) {
9795
- const raw = block?.props?.trigger;
9796
- if (!raw || typeof raw !== "string") return null;
9797
- try {
9798
- const parsed = JSON.parse(raw);
9799
- if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
9800
- return parsed;
10161
+ };
10162
+
10163
+ // src/core/lib/flowEngine/proof.ts
10164
+ var PROOF_MISSING_CODE = "PROOF_MISSING";
10165
+ function getOutputValue(output, path) {
10166
+ return path.split(".").reduce((acc, key) => {
10167
+ if (acc && typeof acc === "object") return acc[key];
10168
+ return void 0;
10169
+ }, output);
10170
+ }
10171
+ function isProofValue(value) {
10172
+ if (Array.isArray(value)) return value.length > 0;
10173
+ if (typeof value === "number") return !Number.isNaN(value);
10174
+ return !!value;
10175
+ }
10176
+ function validateActionProof(action, output) {
10177
+ const proof = action.proof;
10178
+ if (proof === "none") return { valid: true };
10179
+ if (proof === void 0) {
10180
+ if (action.sideEffect) {
10181
+ return {
10182
+ valid: false,
10183
+ reason: `Action '${action.type}' has a side effect but declares no proof of execution. Declare proof (or 'none') on its ActionDefinition.`
10184
+ };
9801
10185
  }
9802
- } catch {
10186
+ return { valid: true };
9803
10187
  }
9804
- return null;
9805
- }
9806
- function parseInputs(block) {
9807
- const raw = block?.props?.inputs;
9808
- if (!raw || typeof raw !== "string") return {};
9809
- try {
9810
- const parsed = JSON.parse(raw);
9811
- if (parsed && typeof parsed === "object") return parsed;
9812
- } catch {
10188
+ if ("validate" in proof) {
10189
+ let valid = false;
10190
+ try {
10191
+ valid = proof.validate(output);
10192
+ } catch {
10193
+ valid = false;
10194
+ }
10195
+ return valid ? { valid: true } : { valid: false, reason: `Action '${action.type}' returned success but its proof validator rejected the output.` };
9813
10196
  }
9814
- return {};
10197
+ const satisfied = proof.fields.some((field) => isProofValue(getOutputValue(output, field)));
10198
+ if (satisfied) return { valid: true };
10199
+ return {
10200
+ valid: false,
10201
+ reason: `Action '${action.type}' returned success without proof. Expected at least one of [${proof.fields.join(", ")}] in the output. Check the handler logs.`
10202
+ };
9815
10203
  }
9816
- function resolveAssignee(block) {
9817
- const raw = block?.props?.assignment;
9818
- if (!raw) return void 0;
9819
- try {
9820
- const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
9821
- const did = parsed?.assignedActor?.did;
9822
- return typeof did === "string" && did.length > 0 ? did : void 0;
9823
- } catch {
9824
- return void 0;
10204
+
10205
+ // src/core/lib/flowEngine/verifiedCompletion.ts
10206
+ function verifyCompletion(params) {
10207
+ const { runtime, blockId, actionType, getInvocation, schemaVersion } = params;
10208
+ if (!runtime || runtime.state !== "completed") {
10209
+ return { status: "not_completed" };
9825
10210
  }
9826
- }
9827
- function computeExpiry(block, emittedAt) {
9828
- const ttlAbsolute = block?.props?.ttlAbsoluteDueDate;
9829
- if (typeof ttlAbsolute === "string" && ttlAbsolute) {
9830
- return ttlAbsolute;
10211
+ if (runtime.manuallyVerified === true) {
10212
+ return { status: "verified" };
9831
10213
  }
9832
- const ttlFromEnablement = block?.props?.ttlFromEnablement;
9833
- if (typeof ttlFromEnablement === "string" && ttlFromEnablement) {
9834
- const ms = parseIsoDurationToMs(ttlFromEnablement);
9835
- if (ms != null) {
9836
- return new Date(new Date(emittedAt).getTime() + ms).toISOString();
10214
+ if (actionType && isRepeatableAction(actionType)) {
10215
+ return { status: "verified" };
10216
+ }
10217
+ if (actionType) {
10218
+ const action = getAction(actionType);
10219
+ if (action) {
10220
+ const proofCheck = validateActionProof(action, runtime.output || {});
10221
+ if (!proofCheck.valid) {
10222
+ return { status: "unverified", reason: proofCheck.reason || "Completed without proof of execution." };
10223
+ }
9837
10224
  }
9838
10225
  }
9839
- return new Date(new Date(emittedAt).getTime() + 7 * 24 * 60 * 60 * 1e3).toISOString();
9840
- }
9841
- function parseIsoDurationToMs(duration) {
9842
- const match = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/.exec(duration);
9843
- if (!match) return null;
9844
- const [, d, h, m, s] = match;
9845
- let ms = 0;
9846
- if (d) ms += parseInt(d, 10) * 24 * 60 * 60 * 1e3;
9847
- if (h) ms += parseInt(h, 10) * 60 * 60 * 1e3;
9848
- if (m) ms += parseInt(m, 10) * 60 * 1e3;
9849
- if (s) ms += parseInt(s, 10) * 1e3;
9850
- return ms;
10226
+ const policy = schemaVersion ? getVersionPolicy(schemaVersion) : null;
10227
+ if (!policy?.ucanRequired) {
10228
+ return { status: "verified" };
10229
+ }
10230
+ const invocationCid = runtime.lastInvocationCid;
10231
+ if (!invocationCid) {
10232
+ return { status: "unverified", reason: "Completed without an invocation record on a UCAN-enforced flow." };
10233
+ }
10234
+ if (!getInvocation) {
10235
+ return { status: "unverified", reason: "No invocation store available to verify the completion." };
10236
+ }
10237
+ const invocation = getInvocation(invocationCid);
10238
+ if (!invocation) {
10239
+ return { status: "unverified", reason: `Invocation ${invocationCid} not found in the invocation store.` };
10240
+ }
10241
+ if (invocation.result !== "success") {
10242
+ return { status: "unverified", reason: "Completion references a failed invocation." };
10243
+ }
10244
+ if (invocation.blockId && invocation.blockId !== blockId) {
10245
+ return { status: "unverified", reason: "Completion references an invocation for a different block." };
10246
+ }
10247
+ return { status: "verified" };
9851
10248
  }
9852
- function getActionForBlock(block) {
9853
- const actionType = block?.props?.actionType;
9854
- if (typeof actionType !== "string") return void 0;
9855
- return getAction(actionType);
10249
+ function isVerifiedCompletion(params) {
10250
+ return verifyCompletion(params).status === "verified";
9856
10251
  }
9857
10252
 
9858
10253
  // src/core/lib/ucanDelegationStore.ts
@@ -10187,26 +10582,6 @@ var createMemoryInvocationStore = () => {
10187
10582
  };
10188
10583
  };
10189
10584
 
10190
- // src/core/lib/flowEngine/emitEvents.ts
10191
- function writeRunRecordAndReconcile(editor, blockId, output, events, actorDid, detailsPatch = {}) {
10192
- const yDoc = editor._yDoc;
10193
- if (!yDoc) return;
10194
- if (events.length === 0) return;
10195
- const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
10196
- const now = (/* @__PURE__ */ new Date()).toISOString();
10197
- const details = {
10198
- runId,
10199
- output,
10200
- events,
10201
- startedAt: now,
10202
- completedAt: now,
10203
- actorDid,
10204
- ...detailsPatch
10205
- };
10206
- appendRunRecord(yDoc, blockId, details, actorDid);
10207
- reconcilePendingInvocations(editor);
10208
- }
10209
-
10210
10585
  // src/core/lib/flowEngine/actionExecutor.ts
10211
10586
  import * as Y3 from "yjs";
10212
10587
 
@@ -10406,6 +10781,46 @@ async function reconcileActionReadBack(params) {
10406
10781
  readBack: failedReadBack
10407
10782
  };
10408
10783
  }
10784
+ const readBackActionType = readBack.actionType || readBack.kind;
10785
+ const actionDef = readBackActionType ? getAction(readBackActionType) : void 0;
10786
+ if (actionDef) {
10787
+ const proofCheck = validateActionProof(actionDef, finalOutput || {});
10788
+ if (!proofCheck.valid) {
10789
+ const message = proofCheck.reason || "Read-back resolved completed without proof of execution.";
10790
+ const proofFailedReadBack = {
10791
+ ...nextReadBack,
10792
+ terminalAt: checkedIso
10793
+ };
10794
+ runtime.update(params.blockId, {
10795
+ state: actionDef.sideEffect ? "needs_verification" : "failed",
10796
+ output: finalOutput,
10797
+ error: { message, code: PROOF_MISSING_CODE, at: checkedAt },
10798
+ readBack: proofFailedReadBack
10799
+ });
10800
+ const failedRunId = writeReconciliationRunRecord({
10801
+ yDoc,
10802
+ editor: getReconcileEditor(params, yDoc, editor),
10803
+ blockId: params.blockId,
10804
+ actorDid,
10805
+ output: finalOutput,
10806
+ events,
10807
+ readBack: proofFailedReadBack,
10808
+ error: { message, code: PROOF_MISSING_CODE },
10809
+ now
10810
+ });
10811
+ return {
10812
+ success: false,
10813
+ blockId: params.blockId,
10814
+ state: "failed",
10815
+ output: finalOutput,
10816
+ events,
10817
+ error: message,
10818
+ runId: failedRunId,
10819
+ pendingInvocationRemoved: false,
10820
+ readBack: proofFailedReadBack
10821
+ };
10822
+ }
10823
+ }
10409
10824
  const completedReadBack = {
10410
10825
  ...nextReadBack,
10411
10826
  terminalAt: checkedIso
@@ -10457,6 +10872,7 @@ function parseInputs2(value) {
10457
10872
  const parsed = JSON.parse(value);
10458
10873
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
10459
10874
  } catch {
10875
+ warnOnce(`executor-inputs-parse:${value}`, `[flow-config] saved block inputs are not valid JSON; treated as empty`);
10460
10876
  return {};
10461
10877
  }
10462
10878
  }
@@ -10638,6 +11054,8 @@ async function executeActionBlock(params) {
10638
11054
  let readBack;
10639
11055
  let rawReadBack;
10640
11056
  let requestedAwaitingReadBack = false;
11057
+ let proofFailureReason = null;
11058
+ let proofFailureOutput;
10641
11059
  const startedAt = now();
10642
11060
  runtime.update(blockId, {
10643
11061
  state: "running",
@@ -10677,6 +11095,13 @@ async function executeActionBlock(params) {
10677
11095
  if (result.completion?.state === "awaiting_readback") {
10678
11096
  requestedAwaitingReadBack = true;
10679
11097
  rawReadBack = result.completion.readBack;
11098
+ } else {
11099
+ const proofCheck = validateActionProof(action, result.output || {});
11100
+ if (!proofCheck.valid) {
11101
+ proofFailureReason = proofCheck.reason || "Action returned success without proof of execution.";
11102
+ proofFailureOutput = result.output || {};
11103
+ throw new Error(proofFailureReason);
11104
+ }
10680
11105
  }
10681
11106
  return {
10682
11107
  payload: result.output,
@@ -10687,6 +11112,27 @@ async function executeActionBlock(params) {
10687
11112
  });
10688
11113
  if (!outcome.success) {
10689
11114
  const message = outcome.error || "Action execution failed";
11115
+ if (proofFailureReason) {
11116
+ const proofFailureState = action.sideEffect ? "needs_verification" : "failed";
11117
+ runtime.update(blockId, {
11118
+ state: proofFailureState,
11119
+ output: proofFailureOutput,
11120
+ error: { message, code: PROOF_MISSING_CODE, at: now() }
11121
+ });
11122
+ return {
11123
+ ...buildFailureResult({
11124
+ blockId,
11125
+ actionType,
11126
+ stage: outcome.stage,
11127
+ error: message,
11128
+ pendingInvocation: inputBuild.pendingInvocation
11129
+ }),
11130
+ completionState: proofFailureState === "needs_verification" ? "needs_verification" : "failed",
11131
+ output: proofFailureOutput,
11132
+ invocationCid: outcome.invocationCid,
11133
+ capabilityId: outcome.capabilityId
11134
+ };
11135
+ }
10690
11136
  updateRuntimeFailure(runtime, blockId, message, now);
10691
11137
  return {
10692
11138
  ...buildFailureResult({
@@ -10754,6 +11200,29 @@ async function executeActionBlock(params) {
10754
11200
  };
10755
11201
  }
10756
11202
 
11203
+ // src/core/lib/flowEngine/validateBlockConfig.ts
11204
+ function validateBlockConfig(block) {
11205
+ const issues = [];
11206
+ if (!block?.props) return issues;
11207
+ const checkJson = (propName, value, expectObject = true) => {
11208
+ if (value == null || value === "") return;
11209
+ if (typeof value !== "string") return;
11210
+ try {
11211
+ const parsed = JSON.parse(value);
11212
+ if (expectObject && (parsed == null || typeof parsed !== "object")) {
11213
+ issues.push(`${propName} is valid JSON but not an object`);
11214
+ }
11215
+ } catch {
11216
+ issues.push(`${propName} is not valid JSON`);
11217
+ }
11218
+ };
11219
+ checkJson("inputs", block.props.inputs);
11220
+ checkJson("trigger", block.props.trigger);
11221
+ checkJson("assignment", block.props.assignment);
11222
+ checkJson("conditions", block.props.conditions);
11223
+ return issues;
11224
+ }
11225
+
10757
11226
  // src/core/lib/flowEngine/migration.ts
10758
11227
  var MIGRATION_REGISTRY = {};
10759
11228
  function registerMigration(definition) {
@@ -12330,20 +12799,30 @@ function classifyBlockerCause(block, runtime) {
12330
12799
  }
12331
12800
  return void 0;
12332
12801
  }
12333
- function classifyNodeState({ block, runtime, now, pendingInvocationCount = 0 }) {
12802
+ function classifyNodeState({
12803
+ block,
12804
+ runtime,
12805
+ now,
12806
+ pendingInvocationCount = 0,
12807
+ completionVerification,
12808
+ isRepeatable = false
12809
+ }) {
12810
+ if (isRepeatable && runtime.state === "completed") return "Active";
12811
+ if (runtime.state === "completed" && completionVerification?.status === "unverified") return "Blocked";
12334
12812
  if (runtime.state === "completed" || runtime.state === "cancelled") return "Done";
12813
+ if (runtime.state === "needs_verification") return "Blocked";
12335
12814
  if (runtime.state === "failed" || runtime.error) return "Blocked";
12336
12815
  const dueAt = getDueAt(block);
12337
12816
  if (dueAt != null && dueAt <= now) return "Overdue";
12338
12817
  if (pendingInvocationCount > 0) return "Pending";
12339
12818
  return "Pending";
12340
12819
  }
12341
- function snapshotNode(block, runtime, now, pendingInvocationCount = 0) {
12820
+ function snapshotNode(block, runtime, now, pendingInvocationCount = 0, completionVerification, isRepeatable = false) {
12342
12821
  const nodeId = getBlockId(block);
12343
12822
  if (!nodeId) {
12344
12823
  throw new Error("Cannot snapshot a block without an id");
12345
12824
  }
12346
- const publicState = classifyNodeState({ block, runtime, now, pendingInvocationCount });
12825
+ const publicState = classifyNodeState({ block, runtime, now, pendingInvocationCount, completionVerification, isRepeatable });
12347
12826
  const dueAt = getDueAt(block);
12348
12827
  const assigneeDid = getAssigneeDid(block);
12349
12828
  const snapshot = {
@@ -12358,7 +12837,13 @@ function snapshotNode(block, runtime, now, pendingInvocationCount = 0) {
12358
12837
  const title = getBlockTitle(block);
12359
12838
  if (title) snapshot.title = title;
12360
12839
  if (publicState === "Blocked") {
12361
- snapshot.blockerCause = classifyBlockerCause(block, runtime) || "unknown";
12840
+ if (runtime.state === "needs_verification") {
12841
+ snapshot.blockerCause = "awaiting_verification";
12842
+ } else if (runtime.state === "completed" && completionVerification?.status === "unverified") {
12843
+ snapshot.blockerCause = "unverified_completion";
12844
+ } else {
12845
+ snapshot.blockerCause = classifyBlockerCause(block, runtime) || "unknown";
12846
+ }
12362
12847
  }
12363
12848
  if (assigneeDid) snapshot.assigneeDid = assigneeDid;
12364
12849
  if (dueAt != null) snapshot.dueAt = dueAt;
@@ -12549,7 +13034,7 @@ function statusForResult(result) {
12549
13034
  function planForSnapshot(context, options, block, snapshot) {
12550
13035
  const queued = [];
12551
13036
  const actionType = getBlockActionType(block);
12552
- if (snapshot.publicState === "Done") {
13037
+ const queueClaimUdidWatch = () => {
12553
13038
  const runtime = snapshot.runtime;
12554
13039
  if (actionType === "qi/claim.submit" && runtime.output?.claimId && !runtime.output?.udid) {
12555
13040
  const command = queueIfAuthorized(context, options, "watch_udid", snapshot.nodeId, "Claim submitted but UDID is not yet observed", {
@@ -12558,6 +13043,13 @@ function planForSnapshot(context, options, block, snapshot) {
12558
13043
  });
12559
13044
  if (command) queued.push(command);
12560
13045
  }
13046
+ };
13047
+ if (snapshot.publicState === "Active") {
13048
+ queueClaimUdidWatch();
13049
+ return queued;
13050
+ }
13051
+ if (snapshot.publicState === "Done") {
13052
+ queueClaimUdidWatch();
12561
13053
  return queued;
12562
13054
  }
12563
13055
  if (snapshot.publicState === "Overdue") {
@@ -12641,11 +13133,24 @@ function planRalphLoopCommands(context, options = {}) {
12641
13133
  const blocks = getBlocks(context);
12642
13134
  const snapshots = [];
12643
13135
  const queuedCommands = [];
13136
+ const invocationsMap = context.yDoc.getMap("invocations");
13137
+ const schemaVersionRaw = context.yDoc.getMap("root").get("schema_version");
13138
+ const schemaVersion = typeof schemaVersionRaw === "string" ? schemaVersionRaw : void 0;
13139
+ const getInvocation = (cid) => invocationsMap.get(cid);
12644
13140
  for (const block of blocks) {
12645
13141
  const nodeId = getBlockId(block);
12646
13142
  if (!nodeId) continue;
12647
13143
  const pendingInvocationCount = readPendingInvocations(context.yDoc, nodeId).length;
12648
- const snapshot = snapshotNode(block, getRuntime3(context.yDoc, nodeId), now, pendingInvocationCount);
13144
+ const runtime = getRuntime3(context.yDoc, nodeId);
13145
+ const actionType = getBlockActionType(block);
13146
+ const completionVerification = verifyCompletion({
13147
+ runtime,
13148
+ blockId: nodeId,
13149
+ actionType,
13150
+ getInvocation,
13151
+ schemaVersion
13152
+ });
13153
+ const snapshot = snapshotNode(block, runtime, now, pendingInvocationCount, completionVerification, isRepeatableAction(actionType));
12649
13154
  snapshots.push(snapshot);
12650
13155
  queuedCommands.push(...planForSnapshot(context, options, block, snapshot));
12651
13156
  }
@@ -13225,14 +13730,20 @@ var FlowAgentService = class {
13225
13730
  };
13226
13731
 
13227
13732
  export {
13733
+ warnOnce,
13734
+ STEP_COMPLETED_EVENT_NAME,
13735
+ STEP_COMPLETED_EVENT,
13228
13736
  resolveActionType,
13229
13737
  registerAction,
13230
13738
  getAction,
13231
13739
  getAllActions,
13740
+ isRepeatableAction,
13741
+ getAliasEntries,
13232
13742
  hasAction,
13233
13743
  getActionByCan,
13234
13744
  getEventsForBlock,
13235
13745
  getOutputSchemaForBlock,
13746
+ generateActionManifest,
13236
13747
  canToType,
13237
13748
  typeToCan,
13238
13749
  getAllCanMappings,
@@ -13256,6 +13767,7 @@ export {
13256
13767
  serializeXeroPaymentCreateInputs,
13257
13768
  parseReferences,
13258
13769
  resolveSingleReference,
13770
+ resolveReferencesDetailed,
13259
13771
  resolveReferences,
13260
13772
  hasReferences,
13261
13773
  createReference,
@@ -13292,15 +13804,7 @@ export {
13292
13804
  formatCoin2 as formatCoin,
13293
13805
  formatCoinAmount,
13294
13806
  createUcanService,
13295
- buildAuthzFromProps,
13296
- buildFlowNodeFromBlock,
13297
- createRuntimeStateManager,
13298
- clearRuntimeForTemplateClone,
13299
13807
  isRuntimeRef,
13300
- resolveRuntimeRefs,
13301
- LATEST_VERSION,
13302
- isAuthorized,
13303
- executeNode,
13304
13808
  RUN_RECORD_AUDIT_TYPE,
13305
13809
  computePendingInvocationId,
13306
13810
  snapshotInputRefs,
@@ -13313,16 +13817,32 @@ export {
13313
13817
  readRunRecords,
13314
13818
  findFailedListenersForSourceRun,
13315
13819
  replayFailedListenerRun,
13820
+ VERSION_MANIFEST,
13821
+ LATEST_VERSION,
13822
+ getVersionPolicy,
13823
+ isVersionAtLeast,
13316
13824
  reconcilePendingInvocations,
13317
13825
  getActionForBlock,
13826
+ writeRunRecordAndReconcile,
13827
+ buildAuthzFromProps,
13828
+ buildFlowNodeFromBlock,
13829
+ createRuntimeStateManager,
13830
+ clearRuntimeForTemplateClone,
13831
+ resolveRuntimeRefs,
13832
+ isAuthorized,
13833
+ executeNode,
13834
+ PROOF_MISSING_CODE,
13835
+ validateActionProof,
13318
13836
  reconcileActionReadBack,
13319
13837
  buildActionRunInputs,
13320
13838
  executeActionBlock,
13839
+ verifyCompletion,
13840
+ isVerifiedCompletion,
13841
+ validateBlockConfig,
13321
13842
  createUcanDelegationStore,
13322
13843
  createMemoryUcanDelegationStore,
13323
13844
  createInvocationStore,
13324
13845
  createMemoryInvocationStore,
13325
- writeRunRecordAndReconcile,
13326
13846
  compileBlockProps,
13327
13847
  COMPILED_BLOCK_TYPE,
13328
13848
  toEvaluatorOperator,
@@ -13374,4 +13894,4 @@ export {
13374
13894
  executeQueuedFlowAgentCoreCommands,
13375
13895
  FlowAgentService
13376
13896
  };
13377
- //# sourceMappingURL=chunk-YWZJYSOP.js.map
13897
+ //# sourceMappingURL=chunk-5TJK6JKV.js.map