@ixo/editor 5.40.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,3 +1,11 @@
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();
3
11
  var STEP_COMPLETED_EVENT_NAME = "step.completed";
@@ -47,6 +55,13 @@ function getAction(type) {
47
55
  function getAllActions() {
48
56
  return Array.from(actions.values());
49
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
+ }
50
65
  function hasAction(type) {
51
66
  return actions.has(resolveActionType(type));
52
67
  }
@@ -62,8 +77,13 @@ function getEventsForBlock(action, inputs) {
62
77
  if (action.getDynamicEvents) {
63
78
  const parsed = normalizeInputs(inputs);
64
79
  try {
65
- events = action.getDynamicEvents(parsed) || [];
66
- } catch {
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
+ );
67
87
  events = [];
68
88
  }
69
89
  } else {
@@ -79,13 +99,23 @@ function getOutputSchemaForBlock(action, inputs) {
79
99
  if (action.getDynamicOutputSchema) {
80
100
  const parsed = normalizeInputs(inputs);
81
101
  try {
82
- return action.getDynamicOutputSchema(parsed) || [];
83
- } 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
+ );
84
110
  return action.outputSchema || [];
85
111
  }
86
112
  }
87
113
  return action.outputSchema || [];
88
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
+ }
89
119
  function normalizeInputs(inputs) {
90
120
  if (!inputs) return {};
91
121
  if (typeof inputs === "object") return inputs;
@@ -94,12 +124,56 @@ function normalizeInputs(inputs) {
94
124
  const parsed = JSON.parse(inputs || "{}");
95
125
  return parsed && typeof parsed === "object" ? parsed : {};
96
126
  } catch {
127
+ warnOnce(`inputs-normalize:${inputs}`, `[flow-config] block inputs are not valid JSON; treated as empty`);
97
128
  return {};
98
129
  }
99
130
  }
100
131
  return {};
101
132
  }
102
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
+
103
177
  // src/core/lib/actionRegistry/canMapping.ts
104
178
  var CAN_TO_TYPE = {
105
179
  "bid/submit": "qi/bid.submit",
@@ -167,7 +241,37 @@ function getAllCanMappings() {
167
241
  }
168
242
 
169
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
+ }
170
273
  function buildServicesFromHandlers(handlers) {
274
+ warnPartialServiceGroups(handlers);
171
275
  return {
172
276
  http: {
173
277
  request: async (params) => {
@@ -375,6 +479,7 @@ registerAction({
375
479
  type: "qi/pod.domain-indexer-lookup",
376
480
  can: "pod/domain-indexer-lookup",
377
481
  sideEffect: false,
482
+ proof: "none",
378
483
  defaultRequiresConfirmation: false,
379
484
  inputSchema: {
380
485
  type: "object",
@@ -414,6 +519,7 @@ registerAction({
414
519
  type: "qi/pod.domain-single-selection",
415
520
  can: "pod/domain-single-selection",
416
521
  sideEffect: false,
522
+ proof: "none",
417
523
  defaultRequiresConfirmation: false,
418
524
  inputSchema: {
419
525
  type: "object",
@@ -468,6 +574,7 @@ registerAction({
468
574
  type: "qi/pod.entity-single-selection",
469
575
  can: "pod/entity-single-selection",
470
576
  sideEffect: false,
577
+ proof: "none",
471
578
  defaultRequiresConfirmation: false,
472
579
  inputSchema: {
473
580
  type: "object",
@@ -526,6 +633,7 @@ registerAction({
526
633
  type: "qi/pod.member-multi-select",
527
634
  can: "pod/member-multi-select",
528
635
  sideEffect: false,
636
+ proof: "none",
529
637
  defaultRequiresConfirmation: false,
530
638
  inputSchema: {
531
639
  type: "object",
@@ -619,6 +727,7 @@ registerAction({
619
727
  type: "qi/pod.governance-config",
620
728
  can: "pod/governance-config",
621
729
  sideEffect: false,
730
+ proof: "none",
622
731
  defaultRequiresConfirmation: false,
623
732
  inputSchema: {
624
733
  type: "object",
@@ -703,6 +812,7 @@ registerAction({
703
812
  type: "qi/pod.list-domain-flows",
704
813
  can: "pod/list-domain-flows",
705
814
  sideEffect: false,
815
+ proof: "none",
706
816
  defaultRequiresConfirmation: false,
707
817
  inputSchema: {
708
818
  type: "object",
@@ -792,6 +902,7 @@ registerAction({
792
902
  type: "qi/governance.member-proposal",
793
903
  can: "governance/member-proposal",
794
904
  sideEffect: true,
905
+ proof: { fields: ["proposalId"] },
795
906
  defaultRequiresConfirmation: true,
796
907
  requiredCapability: "flow/block/execute",
797
908
  outputSchema: [
@@ -807,6 +918,9 @@ registerAction({
807
918
  if (!handlers) {
808
919
  throw new Error("Handlers not available");
809
920
  }
921
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
922
+ throw new Error("Governance proposal handlers not available");
923
+ }
810
924
  const coreAddress = String(inputs.coreAddress || "").trim();
811
925
  if (!coreAddress) throw new Error("coreAddress is required");
812
926
  const operation = String(inputs.operation || "").trim();
@@ -874,6 +988,7 @@ registerAction({
874
988
  type: "qi/governance.settings-proposal",
875
989
  can: "governance/settings-proposal",
876
990
  sideEffect: true,
991
+ proof: { fields: ["proposalId"] },
877
992
  defaultRequiresConfirmation: true,
878
993
  requiredCapability: "flow/block/execute",
879
994
  outputSchema: [
@@ -888,6 +1003,9 @@ registerAction({
888
1003
  if (!handlers) {
889
1004
  throw new Error("Handlers not available");
890
1005
  }
1006
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
1007
+ throw new Error("Governance proposal handlers not available");
1008
+ }
891
1009
  const coreAddress = String(inputs.coreAddress || "").trim();
892
1010
  if (!coreAddress) throw new Error("coreAddress is required");
893
1011
  const votingPeriodHours = Number(inputs.votingPeriodHours);
@@ -950,6 +1068,7 @@ registerAction({
950
1068
  type: "qi/governance.transaction.send-funds",
951
1069
  can: "governance.transaction/send-funds",
952
1070
  sideEffect: true,
1071
+ proof: { fields: ["proposalId"] },
953
1072
  defaultRequiresConfirmation: true,
954
1073
  requiredCapability: "flow/block/execute",
955
1074
  outputSchema: [
@@ -965,6 +1084,9 @@ registerAction({
965
1084
  if (!handlers) {
966
1085
  throw new Error("Handlers not available");
967
1086
  }
1087
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
1088
+ throw new Error("Governance proposal handlers not available");
1089
+ }
968
1090
  const coreAddress = String(inputs.coreAddress || "").trim();
969
1091
  if (!coreAddress) throw new Error("coreAddress is required");
970
1092
  const recipient = String(inputs.recipient || "").trim();
@@ -1008,6 +1130,7 @@ registerAction({
1008
1130
  type: "qi/http.request",
1009
1131
  can: "http/request",
1010
1132
  sideEffect: false,
1133
+ proof: { fields: ["status"] },
1011
1134
  defaultRequiresConfirmation: false,
1012
1135
  // HTTP request can be triggered as a listener — a human assignee is DM'd
1013
1136
  // to invoke it when the upstream event fires. See §3.6 of the
@@ -1068,6 +1191,7 @@ registerAction({
1068
1191
  type: "qi/email.send",
1069
1192
  can: "email/send",
1070
1193
  sideEffect: true,
1194
+ proof: { fields: ["messageId"] },
1071
1195
  defaultRequiresConfirmation: true,
1072
1196
  requiredCapability: "email/send",
1073
1197
  // Email send is autonomous-enough to be triggered by another block's event.
@@ -1133,6 +1257,7 @@ registerAction({
1133
1257
  type: "qi/human.checkbox.set",
1134
1258
  can: "human/checkbox",
1135
1259
  sideEffect: true,
1260
+ proof: "none",
1136
1261
  defaultRequiresConfirmation: false,
1137
1262
  requiredCapability: "flow/execute",
1138
1263
  inputSchema: {
@@ -1174,6 +1299,7 @@ function registerFormSubmitAction(type, can) {
1174
1299
  type,
1175
1300
  can,
1176
1301
  sideEffect: true,
1302
+ proof: "none",
1177
1303
  defaultRequiresConfirmation: false,
1178
1304
  requiredCapability: "flow/execute",
1179
1305
  // Additive: makes the form an event SOURCE so downstream blocks can
@@ -1223,6 +1349,7 @@ registerAction({
1223
1349
  type: "qi/notification.push",
1224
1350
  can: "notification/push",
1225
1351
  sideEffect: true,
1352
+ proof: { fields: ["messageId"] },
1226
1353
  defaultRequiresConfirmation: true,
1227
1354
  requiredCapability: "notify/send",
1228
1355
  inputSchema: {
@@ -1279,6 +1406,7 @@ registerAction({
1279
1406
  type: "qi/bid.submit",
1280
1407
  can: "bid/submit",
1281
1408
  sideEffect: true,
1409
+ proof: { fields: ["bidId"] },
1282
1410
  defaultRequiresConfirmation: true,
1283
1411
  requiredCapability: "flow/block/execute",
1284
1412
  inputSchema: {
@@ -1362,6 +1490,7 @@ registerAction({
1362
1490
  type: "qi/bid.evaluate",
1363
1491
  can: "bid/evaluate",
1364
1492
  sideEffect: true,
1493
+ proof: { fields: ["bidId"] },
1365
1494
  defaultRequiresConfirmation: true,
1366
1495
  requiredCapability: "flow/block/execute",
1367
1496
  outputSchema: [
@@ -1508,7 +1637,7 @@ function extractSurveyAnswerSchema(survey) {
1508
1637
  if (!survey || typeof survey !== "object") return [];
1509
1638
  const json = survey;
1510
1639
  const collected = [];
1511
- const seen = /* @__PURE__ */ new Set();
1640
+ const seen2 = /* @__PURE__ */ new Set();
1512
1641
  const walk = (elements) => {
1513
1642
  if (!Array.isArray(elements)) return;
1514
1643
  for (const el of elements) {
@@ -1519,8 +1648,8 @@ function extractSurveyAnswerSchema(survey) {
1519
1648
  }
1520
1649
  const name = typeof el.name === "string" ? el.name.trim() : "";
1521
1650
  if (!name) continue;
1522
- if (seen.has(name)) continue;
1523
- seen.add(name);
1651
+ if (seen2.has(name)) continue;
1652
+ seen2.add(name);
1524
1653
  const rawType = typeof el.type === "string" ? el.type : "";
1525
1654
  const mapped = TYPE_MAP[rawType] ?? "string";
1526
1655
  let itemSchema;
@@ -1590,6 +1719,13 @@ registerAction({
1590
1719
  type: "qi/claim.submit",
1591
1720
  can: "claim/submit",
1592
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",
1593
1729
  defaultRequiresConfirmation: true,
1594
1730
  requiredCapability: "flow/block/execute",
1595
1731
  eligibleForEventTrigger: true,
@@ -1960,7 +2096,7 @@ function serializeXeroPaymentCreateInputs(inputs) {
1960
2096
  return JSON.stringify(inputs);
1961
2097
  }
1962
2098
 
1963
- // src/mantine/utils/referenceResolver.ts
2099
+ // src/core/lib/flowEngine/referenceResolver.ts
1964
2100
  var REFERENCE_REGEX = /\{\{([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_.:]+)\}\}/g;
1965
2101
  function parseReferences(input) {
1966
2102
  const references = [];
@@ -1982,68 +2118,78 @@ function getNestedValue(obj, path) {
1982
2118
  return current?.[key];
1983
2119
  }, obj);
1984
2120
  }
1985
- function resolveSingleReference(blockId, propPath, editorDocument, yRuntime, scope) {
2121
+ function resolveSingleReferenceDetailed(blockId, propPath, editorDocument, yRuntime, scope) {
1986
2122
  if (scope && Object.prototype.hasOwnProperty.call(scope, blockId)) {
1987
2123
  const root = scope[blockId];
1988
- if (root == null) return void 0;
1989
- 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 };
1990
2127
  }
1991
2128
  if (!editorDocument || !Array.isArray(editorDocument)) {
1992
- return void 0;
2129
+ return { value: void 0, reason: "unknown-block" };
1993
2130
  }
1994
2131
  const block = editorDocument.find((b) => b.id === blockId);
1995
2132
  if (!block) {
1996
- return void 0;
2133
+ return { value: void 0, reason: "unknown-block" };
1997
2134
  }
1998
2135
  if (propPath.startsWith("output.")) {
1999
- if (!yRuntime) return void 0;
2136
+ if (!yRuntime) return { value: void 0, reason: "missing-value" };
2000
2137
  const runtimeState = yRuntime.get(blockId);
2001
- if (!runtimeState?.output) return void 0;
2138
+ if (!runtimeState?.output) return { value: void 0, reason: "missing-value" };
2002
2139
  const innerPath = propPath.substring("output.".length);
2003
2140
  const direct = getNestedValue(runtimeState.output, innerPath);
2004
- if (direct !== void 0) return direct;
2141
+ if (direct !== void 0) return { value: direct };
2005
2142
  if (runtimeState.output.data !== void 0) {
2006
- 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 };
2007
2145
  }
2008
2146
  if (runtimeState.output.http?.data !== void 0) {
2009
- 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 };
2010
2149
  }
2011
- return void 0;
2150
+ return { value: void 0, reason: "missing-value" };
2012
2151
  }
2013
2152
  if (propPath.startsWith("response.")) {
2014
2153
  const responseData = block.props.response;
2015
2154
  if (!responseData) {
2016
- return void 0;
2155
+ return { value: void 0, reason: "missing-value" };
2017
2156
  }
2018
2157
  try {
2019
2158
  const parsedResponse = typeof responseData === "string" ? JSON.parse(responseData) : responseData;
2020
2159
  const innerPath = propPath.substring("response.".length);
2021
2160
  const value2 = getNestedValue(parsedResponse, innerPath);
2022
- return value2;
2023
- } catch (error) {
2024
- 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" };
2025
2165
  }
2026
2166
  }
2027
2167
  const value = getNestedValue(block.props, propPath);
2028
- return value;
2168
+ return { value, reason: value === void 0 ? "missing-value" : void 0 };
2029
2169
  }
2030
- function resolveReferences(input, editorDocument, options = {}) {
2031
- 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 = [];
2032
2176
  if (input == null) {
2033
- return "";
2177
+ return { value: "", unresolved };
2034
2178
  }
2035
2179
  const inputStr = String(input);
2036
2180
  const references = parseReferences(inputStr);
2037
2181
  if (references.length === 0) {
2038
- return inputStr;
2182
+ return { value: inputStr, unresolved };
2039
2183
  }
2040
2184
  let result = inputStr;
2041
2185
  for (let i = references.length - 1; i >= 0; i--) {
2042
2186
  const ref = references[i];
2043
- 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;
2044
2189
  let replacementStr;
2045
2190
  if (resolvedValue === void 0 || resolvedValue === null) {
2046
2191
  replacementStr = fallback;
2192
+ unresolved.push({ ref: ref.fullMatch, blockId: ref.blockId, propPath: ref.propPath, reason: resolution.reason || "missing-value" });
2047
2193
  } else if (typeof resolvedValue === "object") {
2048
2194
  replacementStr = stringifyObjects ? JSON.stringify(resolvedValue) : fallback;
2049
2195
  } else {
@@ -2051,7 +2197,15 @@ function resolveReferences(input, editorDocument, options = {}) {
2051
2197
  }
2052
2198
  result = result.substring(0, ref.startIndex) + replacementStr + result.substring(ref.endIndex);
2053
2199
  }
2054
- 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;
2055
2209
  }
2056
2210
  function hasReferences(input) {
2057
2211
  if (input == null) return false;
@@ -2254,6 +2408,13 @@ registerAction({
2254
2408
  type: "qi/claim.evaluate",
2255
2409
  can: "claim/evaluate",
2256
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",
2257
2418
  defaultRequiresConfirmation: true,
2258
2419
  requiredCapability: "flow/block/execute",
2259
2420
  // Static fallback — used until a collection is picked and the survey
@@ -2547,6 +2708,7 @@ registerAction({
2547
2708
  type: "qi/proposal.create",
2548
2709
  can: "proposal/create",
2549
2710
  sideEffect: true,
2711
+ proof: { fields: ["proposalId"] },
2550
2712
  defaultRequiresConfirmation: true,
2551
2713
  requiredCapability: "flow/block/execute",
2552
2714
  inputSchema: {
@@ -2571,6 +2733,9 @@ registerAction({
2571
2733
  if (!handlers) {
2572
2734
  throw new Error("Handlers not available");
2573
2735
  }
2736
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
2737
+ throw new Error("Governance proposal handlers not available");
2738
+ }
2574
2739
  const coreAddress = String(inputs.coreAddress || "").trim();
2575
2740
  const title = String(inputs.title || "").trim();
2576
2741
  const description = String(inputs.description || "").trim();
@@ -2624,6 +2789,7 @@ registerAction({
2624
2789
  type: "qi/proposal.vote",
2625
2790
  can: "proposal/vote",
2626
2791
  sideEffect: true,
2792
+ proof: { fields: ["votedAt"] },
2627
2793
  defaultRequiresConfirmation: true,
2628
2794
  requiredCapability: "flow/block/execute",
2629
2795
  inputSchema: {
@@ -2647,6 +2813,9 @@ registerAction({
2647
2813
  if (!handlers) {
2648
2814
  throw new Error("Handlers not available");
2649
2815
  }
2816
+ if (!handlers.vote) {
2817
+ throw new Error("vote handler not available");
2818
+ }
2650
2819
  const proposalId = Number(inputs.proposalId);
2651
2820
  const vote = String(inputs.vote || "").trim();
2652
2821
  const rationale = String(inputs.rationale || "").trim();
@@ -2680,6 +2849,7 @@ registerAction({
2680
2849
  type: "qi/protocol.select",
2681
2850
  can: "protocol/select",
2682
2851
  sideEffect: false,
2852
+ proof: "none",
2683
2853
  defaultRequiresConfirmation: false,
2684
2854
  inputSchema: {
2685
2855
  type: "object",
@@ -3959,6 +4129,7 @@ registerAction({
3959
4129
  type: "qi/domain.sign",
3960
4130
  can: "domain/sign",
3961
4131
  sideEffect: true,
4132
+ proof: { fields: ["entityDid"] },
3962
4133
  defaultRequiresConfirmation: true,
3963
4134
  requiredCapability: "flow/block/execute",
3964
4135
  eligibleForEventTrigger: true,
@@ -4109,6 +4280,7 @@ registerAction({
4109
4280
  if (typeof handlers.createDomain !== "function") throw new Error("createDomain handler not implemented");
4110
4281
  if (typeof handlers.createAddLinkedResourceMessage !== "function") throw new Error("createAddLinkedResourceMessage handler not implemented");
4111
4282
  if (typeof handlers.executeTransaction !== "function") throw new Error("executeTransaction handler not implemented");
4283
+ const { requestPin, signCredential, publicFileUpload } = handlers;
4112
4284
  const saveCheckpoint = (updates) => {
4113
4285
  checkpoint = {
4114
4286
  ...checkpoint,
@@ -4173,14 +4345,14 @@ registerAction({
4173
4345
  });
4174
4346
  let signedCredential;
4175
4347
  for (let attempt = 1; attempt <= MAX_PIN_ATTEMPTS; attempt++) {
4176
- const pin = await handlers.requestPin({
4348
+ const pin = await requestPin({
4177
4349
  title: "Sign Domain Card",
4178
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.",
4179
4351
  submitText: "Sign"
4180
4352
  });
4181
4353
  if (!pin) throw new Error("PIN entry cancelled");
4182
4354
  try {
4183
- ({ signedCredential } = await handlers.signCredential({
4355
+ ({ signedCredential } = await signCredential({
4184
4356
  issuerDid,
4185
4357
  issuerType: "user",
4186
4358
  credential: unsignedCredential,
@@ -4199,7 +4371,7 @@ registerAction({
4199
4371
  const credentialFile = new File([credentialBlob], "domainCard.json", {
4200
4372
  type: "application/json"
4201
4373
  });
4202
- const uploadResult = await handlers.publicFileUpload(credentialFile);
4374
+ const uploadResult = await publicFileUpload(credentialFile);
4203
4375
  return {
4204
4376
  ...buildDomainCardLinkedResource({
4205
4377
  entityDid,
@@ -4373,6 +4545,7 @@ registerAction({
4373
4545
  type: "qi/domain.card-preview",
4374
4546
  can: "domain/card-preview",
4375
4547
  sideEffect: false,
4548
+ proof: "none",
4376
4549
  defaultRequiresConfirmation: false,
4377
4550
  inputSchema: {
4378
4551
  type: "object",
@@ -4452,6 +4625,7 @@ registerAction({
4452
4625
  type: "oracle",
4453
4626
  can: "oracle/query",
4454
4627
  sideEffect: false,
4628
+ proof: "none",
4455
4629
  defaultRequiresConfirmation: false,
4456
4630
  inputSchema: {
4457
4631
  type: "object",
@@ -4479,6 +4653,7 @@ registerAction({
4479
4653
  type: "qi/credential.store",
4480
4654
  can: "credential/store",
4481
4655
  sideEffect: true,
4656
+ proof: { fields: ["storedAt"] },
4482
4657
  defaultRequiresConfirmation: true,
4483
4658
  requiredCapability: "flow/execute",
4484
4659
  inputSchema: {
@@ -4540,6 +4715,7 @@ registerAction({
4540
4715
  type: "qi/payment.execute",
4541
4716
  can: "payment/execute",
4542
4717
  sideEffect: true,
4718
+ proof: { fields: ["paymentBlockId"] },
4543
4719
  defaultRequiresConfirmation: true,
4544
4720
  requiredCapability: "flow/block/execute",
4545
4721
  inputSchema: {
@@ -4624,6 +4800,7 @@ registerAction({
4624
4800
  type: "qi/matrix.dm",
4625
4801
  can: "matrix/dm",
4626
4802
  sideEffect: true,
4803
+ proof: { fields: ["roomId"] },
4627
4804
  defaultRequiresConfirmation: false,
4628
4805
  inputSchema: {
4629
4806
  type: "object",
@@ -4661,6 +4838,7 @@ registerAction({
4661
4838
  type: "qi/wallet.generate",
4662
4839
  can: "wallet/generate",
4663
4840
  sideEffect: false,
4841
+ proof: { fields: ["address"] },
4664
4842
  defaultRequiresConfirmation: false,
4665
4843
  inputSchema: {
4666
4844
  type: "object",
@@ -4687,6 +4865,7 @@ registerAction({
4687
4865
  type: "qi/wallet.fund",
4688
4866
  can: "wallet/fund",
4689
4867
  sideEffect: true,
4868
+ proof: { fields: ["transactionHash"] },
4690
4869
  defaultRequiresConfirmation: true,
4691
4870
  inputSchema: {
4692
4871
  type: "object",
@@ -4715,6 +4894,7 @@ registerAction({
4715
4894
  type: "qi/wallet.generateAndFund",
4716
4895
  can: "wallet/generateAndFund",
4717
4896
  sideEffect: true,
4897
+ proof: { fields: ["transactionHash"] },
4718
4898
  defaultRequiresConfirmation: false,
4719
4899
  inputSchema: {
4720
4900
  type: "object",
@@ -4765,6 +4945,7 @@ registerAction({
4765
4945
  type: "qi/iid.create",
4766
4946
  can: "iid/create",
4767
4947
  sideEffect: true,
4948
+ proof: { fields: ["transactionHash", "alreadyExisted"] },
4768
4949
  defaultRequiresConfirmation: false,
4769
4950
  inputSchema: {
4770
4951
  type: "object",
@@ -4803,6 +4984,7 @@ registerAction({
4803
4984
  type: "qi/matrix.register",
4804
4985
  can: "matrix/register",
4805
4986
  sideEffect: true,
4987
+ proof: { fields: ["matrixUserId"] },
4806
4988
  defaultRequiresConfirmation: false,
4807
4989
  inputSchema: {
4808
4990
  type: "object",
@@ -4852,6 +5034,8 @@ registerAction({
4852
5034
  type: "qi/identity.create",
4853
5035
  can: "identity/create",
4854
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 },
4855
5039
  defaultRequiresConfirmation: false,
4856
5040
  inputSchema: {
4857
5041
  type: "object",
@@ -4952,6 +5136,7 @@ registerAction({
4952
5136
  type: "qi/entity.createOracle",
4953
5137
  can: "entity/createOracle",
4954
5138
  sideEffect: true,
5139
+ proof: { fields: ["entityDid"] },
4955
5140
  defaultRequiresConfirmation: false,
4956
5141
  inputSchema: {
4957
5142
  type: "object",
@@ -5054,6 +5239,7 @@ registerAction({
5054
5239
  type: "qi/sandbox.provision",
5055
5240
  can: "sandbox/provision",
5056
5241
  sideEffect: true,
5242
+ proof: { fields: ["status", "sandboxUrl"] },
5057
5243
  defaultRequiresConfirmation: false,
5058
5244
  inputSchema: {
5059
5245
  type: "object",
@@ -5086,6 +5272,7 @@ registerAction({
5086
5272
  type: "qi/oracle.contract",
5087
5273
  can: "oracle/contract",
5088
5274
  sideEffect: true,
5275
+ proof: { fields: ["userOracleRoomId"] },
5089
5276
  defaultRequiresConfirmation: false,
5090
5277
  inputSchema: {
5091
5278
  type: "object",
@@ -5116,6 +5303,7 @@ registerAction({
5116
5303
  type: "qi/oracle.storeSecrets",
5117
5304
  can: "oracle/storeSecrets",
5118
5305
  sideEffect: true,
5306
+ proof: { fields: ["storedSecrets"] },
5119
5307
  defaultRequiresConfirmation: false,
5120
5308
  inputSchema: {
5121
5309
  type: "object",
@@ -5206,6 +5394,7 @@ registerAction({
5206
5394
  type: "qi/oracle.storeConfig",
5207
5395
  can: "oracle/storeConfig",
5208
5396
  sideEffect: true,
5397
+ proof: { fields: ["configStored"] },
5209
5398
  defaultRequiresConfirmation: false,
5210
5399
  inputSchema: {
5211
5400
  type: "object",
@@ -5276,6 +5465,7 @@ registerAction({
5276
5465
  type: "qi/oracle.storeSecretsAndConfig",
5277
5466
  can: "oracle/storeSecretsAndConfig",
5278
5467
  sideEffect: true,
5468
+ proof: { fields: ["storedSecrets"] },
5279
5469
  defaultRequiresConfirmation: false,
5280
5470
  inputSchema: {
5281
5471
  type: "object",
@@ -5427,6 +5617,7 @@ registerAction({
5427
5617
  type: "qi/oracle.configureOracle",
5428
5618
  can: "oracle/configureOracle",
5429
5619
  sideEffect: true,
5620
+ proof: { fields: ["configStored"] },
5430
5621
  defaultRequiresConfirmation: false,
5431
5622
  inputSchema: {
5432
5623
  type: "object",
@@ -5607,6 +5798,7 @@ registerAction({
5607
5798
  type: "qi/oracle.deploySetup",
5608
5799
  can: "oracle/deploySetup",
5609
5800
  sideEffect: true,
5801
+ proof: { fields: ["setupComplete"] },
5610
5802
  defaultRequiresConfirmation: false,
5611
5803
  inputSchema: {
5612
5804
  type: "object",
@@ -5641,6 +5833,7 @@ registerAction({
5641
5833
  type: "qi/oracle.deployStart",
5642
5834
  can: "oracle/deployStart",
5643
5835
  sideEffect: true,
5836
+ proof: { fields: ["processId", "status"] },
5644
5837
  defaultRequiresConfirmation: false,
5645
5838
  inputSchema: {
5646
5839
  type: "object",
@@ -5674,6 +5867,7 @@ registerAction({
5674
5867
  type: "qi/oracle.deploy",
5675
5868
  can: "oracle/deploy",
5676
5869
  sideEffect: true,
5870
+ proof: { fields: ["processId", "status"] },
5677
5871
  defaultRequiresConfirmation: false,
5678
5872
  inputSchema: {
5679
5873
  type: "object",
@@ -5809,6 +6003,10 @@ registerAction({
5809
6003
  type: COLLECTION_LIFECYCLE_ACTION_TYPE,
5810
6004
  can: "collection/lifecycle",
5811
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"] },
5812
6010
  defaultRequiresConfirmation: true,
5813
6011
  requiredCapability: "flow/block/execute",
5814
6012
  outputSchema: COLLECTION_LIFECYCLE_OUTPUT_SCHEMA,
@@ -6005,6 +6203,13 @@ registerAction({
6005
6203
  type: COLLECTION_USERS_ACTION_TYPE,
6006
6204
  can: "collection/users",
6007
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",
6008
6213
  defaultRequiresConfirmation: true,
6009
6214
  requiredCapability: "flow/block/execute",
6010
6215
  outputSchema: COLLECTION_USERS_OUTPUT_SCHEMA,
@@ -6168,6 +6373,7 @@ registerAction({
6168
6373
  type: "qi/carbon.loadBatches",
6169
6374
  can: "carbon/load",
6170
6375
  sideEffect: false,
6376
+ proof: "none",
6171
6377
  defaultRequiresConfirmation: false,
6172
6378
  inputSchema: {
6173
6379
  type: "object",
@@ -6194,6 +6400,7 @@ registerAction({
6194
6400
  type: "qi/carbon.harvest",
6195
6401
  can: "carbon/harvest",
6196
6402
  sideEffect: true,
6403
+ proof: { fields: ["transactionHash"] },
6197
6404
  defaultRequiresConfirmation: true,
6198
6405
  inputSchema: {
6199
6406
  type: "object",
@@ -6233,6 +6440,7 @@ registerAction({
6233
6440
  type: "qi/carbon.retire",
6234
6441
  can: "carbon/retire",
6235
6442
  sideEffect: true,
6443
+ proof: { fields: ["transactionHash"] },
6236
6444
  defaultRequiresConfirmation: true,
6237
6445
  inputSchema: {
6238
6446
  type: "object",
@@ -6294,6 +6502,7 @@ registerAction({
6294
6502
  type: "qi/entity.transfer",
6295
6503
  can: "entity/transfer",
6296
6504
  sideEffect: true,
6505
+ proof: { fields: ["transactionHash"] },
6297
6506
  defaultRequiresConfirmation: true,
6298
6507
  outputSchema: ENTITY_TRANSFER_OUTPUT,
6299
6508
  inputSchema: {
@@ -6439,6 +6648,8 @@ registerAction({
6439
6648
  type: "qi/gmail.email.send",
6440
6649
  can: "gmail.email/send",
6441
6650
  sideEffect: true,
6651
+ // Proof of execution: Gmail returns the sent message id. Matches qi/email.send.
6652
+ proof: { fields: ["messageId"] },
6442
6653
  defaultRequiresConfirmation: true,
6443
6654
  requiredCapability: "flow/block/execute",
6444
6655
  // Can be wired to another block's event (e.g. form submitted → send email).
@@ -6511,6 +6722,11 @@ registerAction({
6511
6722
  type: "qi/outlook.email.send",
6512
6723
  can: "outlook.email/send",
6513
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",
6514
6730
  defaultRequiresConfirmation: true,
6515
6731
  requiredCapability: "flow/block/execute",
6516
6732
  // Can be wired to another block's event (e.g. form submitted → send email).
@@ -6583,6 +6799,8 @@ registerAction({
6583
6799
  type: "qi/slack.message.send",
6584
6800
  can: "slack.message/send",
6585
6801
  sideEffect: true,
6802
+ // Proof of execution: Slack returns the posted message timestamp (ts).
6803
+ proof: { fields: ["messageTs"] },
6586
6804
  defaultRequiresConfirmation: true,
6587
6805
  requiredCapability: "flow/block/execute",
6588
6806
  // Can be wired to another block's event (e.g. form submitted → post message).
@@ -6658,6 +6876,8 @@ registerAction({
6658
6876
  type: "qi/googlecalendar.event.create",
6659
6877
  can: "googlecalendar.event/create",
6660
6878
  sideEffect: true,
6879
+ // Proof of execution: the created event's id. Matches qi/calendar.event.create.
6880
+ proof: { fields: ["eventId"] },
6661
6881
  defaultRequiresConfirmation: true,
6662
6882
  requiredCapability: "flow/block/execute",
6663
6883
  eligibleForEventTrigger: true,
@@ -6754,6 +6974,7 @@ registerAction({
6754
6974
  type: "qi/calendar.event.create",
6755
6975
  can: "calendar.event/create",
6756
6976
  sideEffect: true,
6977
+ proof: { fields: ["eventId"] },
6757
6978
  defaultRequiresConfirmation: true,
6758
6979
  requiredCapability: "flow/block/execute",
6759
6980
  eligibleForEventTrigger: true,
@@ -6861,6 +7082,7 @@ registerAction({
6861
7082
  type: "qi/calendar.event.update",
6862
7083
  can: "calendar.event/update",
6863
7084
  sideEffect: true,
7085
+ proof: { fields: ["eventId"] },
6864
7086
  defaultRequiresConfirmation: true,
6865
7087
  requiredCapability: "flow/block/execute",
6866
7088
  eligibleForEventTrigger: true,
@@ -6970,6 +7192,7 @@ registerAction({
6970
7192
  type: "qi/calendar.event.list",
6971
7193
  can: "calendar.event/list",
6972
7194
  sideEffect: false,
7195
+ proof: "none",
6973
7196
  defaultRequiresConfirmation: false,
6974
7197
  requiredCapability: "flow/block/execute",
6975
7198
  eligibleForEventTrigger: false,
@@ -7051,6 +7274,7 @@ registerAction({
7051
7274
  type: "qi/xero.contact.create",
7052
7275
  can: "xero.contact/create",
7053
7276
  sideEffect: true,
7277
+ proof: { fields: ["contactId"] },
7054
7278
  defaultRequiresConfirmation: true,
7055
7279
  requiredCapability: "flow/block/execute",
7056
7280
  eligibleForEventTrigger: true,
@@ -7149,6 +7373,10 @@ registerAction({
7149
7373
  type: "qi/xero.invoice.create",
7150
7374
  can: "xero.invoice/create",
7151
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",
7152
7380
  defaultRequiresConfirmation: true,
7153
7381
  requiredCapability: "flow/block/execute",
7154
7382
  eligibleForEventTrigger: true,
@@ -7254,6 +7482,7 @@ registerAction({
7254
7482
  type: "qi/xero.invoice.list",
7255
7483
  can: "xero.invoice/list",
7256
7484
  sideEffect: false,
7485
+ proof: "none",
7257
7486
  defaultRequiresConfirmation: false,
7258
7487
  requiredCapability: "flow/block/execute",
7259
7488
  eligibleForEventTrigger: false,
@@ -7334,6 +7563,10 @@ registerAction({
7334
7563
  type: "qi/xero.payment.create",
7335
7564
  can: "xero.payment/create",
7336
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",
7337
7570
  defaultRequiresConfirmation: true,
7338
7571
  requiredCapability: "flow/block/execute",
7339
7572
  eligibleForEventTrigger: true,
@@ -9065,7 +9298,7 @@ function snapshotInputRefs(inputs, getNodeOutput2) {
9065
9298
  if (!parsed) return;
9066
9299
  const output = getNodeOutput2(parsed.nodeId);
9067
9300
  if (!output) return;
9068
- snapshots[ref.$ref] = getNestedValue2(output, parsed.fieldPath);
9301
+ snapshots[ref.$ref] = getNestedValue(output, parsed.fieldPath);
9069
9302
  });
9070
9303
  return snapshots;
9071
9304
  }
@@ -9090,15 +9323,6 @@ function parseOutputRef(ref) {
9090
9323
  fieldPath: ref.slice(outputIndex + ".output.".length)
9091
9324
  };
9092
9325
  }
9093
- function getNestedValue2(obj, path) {
9094
- const parts = path.split(".");
9095
- let current = obj;
9096
- for (const part of parts) {
9097
- if (current == null || typeof current !== "object") return void 0;
9098
- current = current[part];
9099
- }
9100
- return current;
9101
- }
9102
9326
  function fnv1a32(input) {
9103
9327
  let hash = 2166136261;
9104
9328
  for (let i = 0; i < input.length; i++) {
@@ -9151,6 +9375,32 @@ function removePendingInvocation(yDoc, listenerBlockId, pendingInvocationId) {
9151
9375
  inner.delete(pendingInvocationId);
9152
9376
  return true;
9153
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);
9382
+ }
9383
+ return consumed;
9384
+ }
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
+ }
9154
9404
  var BARRIER_STATE_MAP_KEY = "barrierState";
9155
9405
  function getBarrierStateMap(yDoc) {
9156
9406
  return yDoc.getMap(BARRIER_STATE_MAP_KEY);
@@ -9281,6 +9531,63 @@ function replayFailedListenerRun(yDoc, failedRecord, listenerBlockId, originalPa
9281
9531
  return queuePendingInvocation(yDoc, listenerBlockId, replay);
9282
9532
  }
9283
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
+ },
9553
+ description: "Legacy version. UCAN optional, whitelist-only authorization accepted."
9554
+ },
9555
+ "1.0.0": {
9556
+ version: "1.0.0",
9557
+ label: "UCAN Required",
9558
+ ucanRequired: true,
9559
+ delegationRootRequired: true,
9560
+ whitelistOnlyAllowed: false,
9561
+ unrestrictedAllowed: false,
9562
+ executionPath: "invocation",
9563
+ authorizationFn: "v2",
9564
+ allowedAuthModes: ["capability"],
9565
+ ui: {
9566
+ showDelegationPanel: true,
9567
+ showWhitelistConfig: false,
9568
+ showAnyoneConfig: false,
9569
+ showMigrationBanner: false,
9570
+ requirePinForExecution: true
9571
+ },
9572
+ description: "UCAN-enforced. Every block execution requires a valid delegation chain."
9573
+ }
9574
+ };
9575
+ var LATEST_VERSION = "1.0.0";
9576
+ var MIGRATION_PATH = ["0.3", "1.0.0"];
9577
+ function getVersionPolicy(version) {
9578
+ const policy = VERSION_MANIFEST[version];
9579
+ if (!policy) {
9580
+ return VERSION_MANIFEST[LATEST_VERSION];
9581
+ }
9582
+ return policy;
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
+ }
9590
+
9284
9591
  // src/core/lib/flowEngine/reconcile.ts
9285
9592
  var _reconcileRunning = false;
9286
9593
  function reconcilePendingInvocations(editor) {
@@ -9317,6 +9624,20 @@ function _reconcilePendingInvocationsInner(editor) {
9317
9624
  }
9318
9625
  }
9319
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");
9320
9641
  const runtimeMap = editor._yRuntime;
9321
9642
  const getNodeOutput2 = (nodeId) => {
9322
9643
  if (!runtimeMap) return void 0;
@@ -9344,12 +9665,18 @@ function _reconcilePendingInvocationsInner(editor) {
9344
9665
  if (!hasAnyListener) continue;
9345
9666
  const records = readRunRecords(yDoc, sourceBlockId);
9346
9667
  for (const record of records) {
9347
- processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNodeOutput2, runtimeMap);
9348
- processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap);
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);
9349
9671
  }
9350
9672
  }
9351
9673
  }
9352
- function processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNodeOutput2, runtimeMap) {
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) {
9353
9680
  if (!Array.isArray(record.events)) return;
9354
9681
  record.events.forEach((event, eventIndex) => {
9355
9682
  if (!event?.name) return;
@@ -9366,10 +9693,12 @@ function processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNod
9366
9693
  eventName: event.name,
9367
9694
  eventIndex
9368
9695
  });
9696
+ if (isConsumed(listenerBlockId, id)) continue;
9369
9697
  const assigneeDid = resolveAssignee(listenerBlock) || "unassigned";
9370
9698
  const inputs = parseInputs(listenerBlock);
9371
9699
  const refSnapshots = snapshotInputRefs(inputs, getNodeOutput2);
9372
9700
  const expiresAt = computeExpiry(listenerBlock, record.completedAt);
9701
+ if (expiresAt && Date.parse(expiresAt) < now) continue;
9373
9702
  const invocation = {
9374
9703
  id,
9375
9704
  triggeringBlockId: sourceBlockId,
@@ -9393,7 +9722,7 @@ function processRunRecord(yDoc, sourceBlockId, record, listenersBySource, getNod
9393
9722
  }
9394
9723
  });
9395
9724
  }
9396
- function processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap) {
9725
+ function processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBySource, getNodeOutput2, runtimeMap, isConsumed, now) {
9397
9726
  if (!Array.isArray(record.events)) return;
9398
9727
  for (const event of record.events) {
9399
9728
  if (!event?.name) continue;
@@ -9428,10 +9757,12 @@ function processBarrierRunRecord(yDoc, sourceBlockId, record, barrierListenersBy
9428
9757
  if (!allFired) continue;
9429
9758
  const assigneeDid = resolveAssignee(listenerBlock) || "unassigned";
9430
9759
  const id = computeBarrierInvocationId(currentState, listenerBlockId);
9760
+ if (isConsumed(listenerBlockId, id)) continue;
9431
9761
  const mergedPayload = mergeBarrierPayloads(currentState);
9432
9762
  const inputs = parseInputs(listenerBlock);
9433
9763
  const refSnapshots = snapshotInputRefs(inputs, getNodeOutput2);
9434
9764
  const expiresAt = computeExpiry(listenerBlock, record.completedAt);
9765
+ if (expiresAt && Date.parse(expiresAt) < now) continue;
9435
9766
  const invocation = {
9436
9767
  id,
9437
9768
  triggeringBlockId: sourceBlockId,
@@ -9465,6 +9796,7 @@ function parseTrigger(block) {
9465
9796
  return parsed;
9466
9797
  }
9467
9798
  } catch {
9799
+ warnOnce(`trigger-parse:${block?.id}`, `[flow-config] block ${block?.id}: props.trigger is not valid JSON; trigger ignored`);
9468
9800
  }
9469
9801
  return null;
9470
9802
  }
@@ -9475,6 +9807,7 @@ function parseInputs(block) {
9475
9807
  const parsed = JSON.parse(raw);
9476
9808
  if (parsed && typeof parsed === "object") return parsed;
9477
9809
  } catch {
9810
+ warnOnce(`inputs-parse:${block?.id}`, `[flow-config] block ${block?.id}: props.inputs is not valid JSON; treated as empty`);
9478
9811
  }
9479
9812
  return {};
9480
9813
  }
@@ -9486,6 +9819,7 @@ function resolveAssignee(block) {
9486
9819
  const did = parsed?.assignedActor?.did;
9487
9820
  return typeof did === "string" && did.length > 0 ? did : void 0;
9488
9821
  } catch {
9822
+ warnOnce(`assignment-parse:${block?.id}`, `[flow-config] block ${block?.id}: props.assignment is not valid JSON; assignee unresolved`);
9489
9823
  return void 0;
9490
9824
  }
9491
9825
  }
@@ -9649,7 +9983,7 @@ function resolveRef(ref, getNodeOutput2, triggerContext) {
9649
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.`);
9650
9984
  }
9651
9985
  const fieldPath2 = ref.slice("trigger.payload.".length);
9652
- return getNestedValue3(triggerContext.payload, fieldPath2);
9986
+ return getNestedValue(triggerContext.payload, fieldPath2);
9653
9987
  }
9654
9988
  if (triggerContext && Object.prototype.hasOwnProperty.call(triggerContext.refSnapshots, ref)) {
9655
9989
  return triggerContext.refSnapshots[ref];
@@ -9664,69 +9998,7 @@ function resolveRef(ref, getNodeOutput2, triggerContext) {
9664
9998
  if (!output) {
9665
9999
  return void 0;
9666
10000
  }
9667
- return getNestedValue3(output, fieldPath);
9668
- }
9669
- function getNestedValue3(obj, path) {
9670
- const parts = path.split(".");
9671
- let current = obj;
9672
- for (const part of parts) {
9673
- if (current == null || typeof current !== "object") {
9674
- return void 0;
9675
- }
9676
- current = current[part];
9677
- }
9678
- return current;
9679
- }
9680
-
9681
- // src/core/lib/flowEngine/versionManifest.ts
9682
- var VERSION_MANIFEST = {
9683
- "0.3": {
9684
- version: "0.3",
9685
- label: "Legacy",
9686
- ucanRequired: false,
9687
- delegationRootRequired: false,
9688
- whitelistOnlyAllowed: true,
9689
- unrestrictedAllowed: true,
9690
- executionPath: "legacy",
9691
- authorizationFn: "v1",
9692
- allowedAuthModes: ["anyone", "actors", "capability"],
9693
- ui: {
9694
- showDelegationPanel: false,
9695
- showWhitelistConfig: true,
9696
- showAnyoneConfig: true,
9697
- showMigrationBanner: true,
9698
- requirePinForExecution: false
9699
- },
9700
- description: "Legacy version. UCAN optional, whitelist-only authorization accepted."
9701
- },
9702
- "1.0.0": {
9703
- version: "1.0.0",
9704
- label: "UCAN Required",
9705
- /** TEMP Disablement - needs to be true TODO */
9706
- ucanRequired: false,
9707
- delegationRootRequired: true,
9708
- whitelistOnlyAllowed: false,
9709
- unrestrictedAllowed: false,
9710
- executionPath: "invocation",
9711
- authorizationFn: "v2",
9712
- allowedAuthModes: ["capability"],
9713
- ui: {
9714
- showDelegationPanel: true,
9715
- showWhitelistConfig: false,
9716
- showAnyoneConfig: false,
9717
- showMigrationBanner: false,
9718
- requirePinForExecution: true
9719
- },
9720
- description: "UCAN-enforced. Every block execution requires a valid delegation chain."
9721
- }
9722
- };
9723
- var LATEST_VERSION = "1.0.0";
9724
- function getVersionPolicy(version) {
9725
- const policy = VERSION_MANIFEST[version];
9726
- if (!policy) {
9727
- return VERSION_MANIFEST[LATEST_VERSION];
9728
- }
9729
- return policy;
10001
+ return getNestedValue(output, fieldPath);
9730
10002
  }
9731
10003
 
9732
10004
  // src/core/lib/flowEngine/authorization.ts
@@ -9888,6 +10160,96 @@ var executeNode = async ({ node, actorDid, actorType, entityRoomId, context, act
9888
10160
  }
9889
10161
  };
9890
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
+ };
10185
+ }
10186
+ return { valid: true };
10187
+ }
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.` };
10196
+ }
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
+ };
10203
+ }
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" };
10210
+ }
10211
+ if (runtime.manuallyVerified === true) {
10212
+ return { status: "verified" };
10213
+ }
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
+ }
10224
+ }
10225
+ }
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" };
10248
+ }
10249
+ function isVerifiedCompletion(params) {
10250
+ return verifyCompletion(params).status === "verified";
10251
+ }
10252
+
9891
10253
  // src/core/lib/ucanDelegationStore.ts
9892
10254
  var ROOT_DELEGATION_KEY = "__root__";
9893
10255
  var STORE_VERSION_KEY = "__version__";
@@ -10419,6 +10781,46 @@ async function reconcileActionReadBack(params) {
10419
10781
  readBack: failedReadBack
10420
10782
  };
10421
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
+ }
10422
10824
  const completedReadBack = {
10423
10825
  ...nextReadBack,
10424
10826
  terminalAt: checkedIso
@@ -10470,6 +10872,7 @@ function parseInputs2(value) {
10470
10872
  const parsed = JSON.parse(value);
10471
10873
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
10472
10874
  } catch {
10875
+ warnOnce(`executor-inputs-parse:${value}`, `[flow-config] saved block inputs are not valid JSON; treated as empty`);
10473
10876
  return {};
10474
10877
  }
10475
10878
  }
@@ -10651,6 +11054,8 @@ async function executeActionBlock(params) {
10651
11054
  let readBack;
10652
11055
  let rawReadBack;
10653
11056
  let requestedAwaitingReadBack = false;
11057
+ let proofFailureReason = null;
11058
+ let proofFailureOutput;
10654
11059
  const startedAt = now();
10655
11060
  runtime.update(blockId, {
10656
11061
  state: "running",
@@ -10690,6 +11095,13 @@ async function executeActionBlock(params) {
10690
11095
  if (result.completion?.state === "awaiting_readback") {
10691
11096
  requestedAwaitingReadBack = true;
10692
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
+ }
10693
11105
  }
10694
11106
  return {
10695
11107
  payload: result.output,
@@ -10700,6 +11112,27 @@ async function executeActionBlock(params) {
10700
11112
  });
10701
11113
  if (!outcome.success) {
10702
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
+ }
10703
11136
  updateRuntimeFailure(runtime, blockId, message, now);
10704
11137
  return {
10705
11138
  ...buildFailureResult({
@@ -10767,6 +11200,29 @@ async function executeActionBlock(params) {
10767
11200
  };
10768
11201
  }
10769
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
+
10770
11226
  // src/core/lib/flowEngine/migration.ts
10771
11227
  var MIGRATION_REGISTRY = {};
10772
11228
  function registerMigration(definition) {
@@ -12343,20 +12799,30 @@ function classifyBlockerCause(block, runtime) {
12343
12799
  }
12344
12800
  return void 0;
12345
12801
  }
12346
- 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";
12347
12812
  if (runtime.state === "completed" || runtime.state === "cancelled") return "Done";
12813
+ if (runtime.state === "needs_verification") return "Blocked";
12348
12814
  if (runtime.state === "failed" || runtime.error) return "Blocked";
12349
12815
  const dueAt = getDueAt(block);
12350
12816
  if (dueAt != null && dueAt <= now) return "Overdue";
12351
12817
  if (pendingInvocationCount > 0) return "Pending";
12352
12818
  return "Pending";
12353
12819
  }
12354
- function snapshotNode(block, runtime, now, pendingInvocationCount = 0) {
12820
+ function snapshotNode(block, runtime, now, pendingInvocationCount = 0, completionVerification, isRepeatable = false) {
12355
12821
  const nodeId = getBlockId(block);
12356
12822
  if (!nodeId) {
12357
12823
  throw new Error("Cannot snapshot a block without an id");
12358
12824
  }
12359
- const publicState = classifyNodeState({ block, runtime, now, pendingInvocationCount });
12825
+ const publicState = classifyNodeState({ block, runtime, now, pendingInvocationCount, completionVerification, isRepeatable });
12360
12826
  const dueAt = getDueAt(block);
12361
12827
  const assigneeDid = getAssigneeDid(block);
12362
12828
  const snapshot = {
@@ -12371,7 +12837,13 @@ function snapshotNode(block, runtime, now, pendingInvocationCount = 0) {
12371
12837
  const title = getBlockTitle(block);
12372
12838
  if (title) snapshot.title = title;
12373
12839
  if (publicState === "Blocked") {
12374
- 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
+ }
12375
12847
  }
12376
12848
  if (assigneeDid) snapshot.assigneeDid = assigneeDid;
12377
12849
  if (dueAt != null) snapshot.dueAt = dueAt;
@@ -12562,7 +13034,7 @@ function statusForResult(result) {
12562
13034
  function planForSnapshot(context, options, block, snapshot) {
12563
13035
  const queued = [];
12564
13036
  const actionType = getBlockActionType(block);
12565
- if (snapshot.publicState === "Done") {
13037
+ const queueClaimUdidWatch = () => {
12566
13038
  const runtime = snapshot.runtime;
12567
13039
  if (actionType === "qi/claim.submit" && runtime.output?.claimId && !runtime.output?.udid) {
12568
13040
  const command = queueIfAuthorized(context, options, "watch_udid", snapshot.nodeId, "Claim submitted but UDID is not yet observed", {
@@ -12571,6 +13043,13 @@ function planForSnapshot(context, options, block, snapshot) {
12571
13043
  });
12572
13044
  if (command) queued.push(command);
12573
13045
  }
13046
+ };
13047
+ if (snapshot.publicState === "Active") {
13048
+ queueClaimUdidWatch();
13049
+ return queued;
13050
+ }
13051
+ if (snapshot.publicState === "Done") {
13052
+ queueClaimUdidWatch();
12574
13053
  return queued;
12575
13054
  }
12576
13055
  if (snapshot.publicState === "Overdue") {
@@ -12654,11 +13133,24 @@ function planRalphLoopCommands(context, options = {}) {
12654
13133
  const blocks = getBlocks(context);
12655
13134
  const snapshots = [];
12656
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);
12657
13140
  for (const block of blocks) {
12658
13141
  const nodeId = getBlockId(block);
12659
13142
  if (!nodeId) continue;
12660
13143
  const pendingInvocationCount = readPendingInvocations(context.yDoc, nodeId).length;
12661
- 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));
12662
13154
  snapshots.push(snapshot);
12663
13155
  queuedCommands.push(...planForSnapshot(context, options, block, snapshot));
12664
13156
  }
@@ -13238,16 +13730,20 @@ var FlowAgentService = class {
13238
13730
  };
13239
13731
 
13240
13732
  export {
13733
+ warnOnce,
13241
13734
  STEP_COMPLETED_EVENT_NAME,
13242
13735
  STEP_COMPLETED_EVENT,
13243
13736
  resolveActionType,
13244
13737
  registerAction,
13245
13738
  getAction,
13246
13739
  getAllActions,
13740
+ isRepeatableAction,
13741
+ getAliasEntries,
13247
13742
  hasAction,
13248
13743
  getActionByCan,
13249
13744
  getEventsForBlock,
13250
13745
  getOutputSchemaForBlock,
13746
+ generateActionManifest,
13251
13747
  canToType,
13252
13748
  typeToCan,
13253
13749
  getAllCanMappings,
@@ -13271,6 +13767,7 @@ export {
13271
13767
  serializeXeroPaymentCreateInputs,
13272
13768
  parseReferences,
13273
13769
  resolveSingleReference,
13770
+ resolveReferencesDetailed,
13274
13771
  resolveReferences,
13275
13772
  hasReferences,
13276
13773
  createReference,
@@ -13320,6 +13817,10 @@ export {
13320
13817
  readRunRecords,
13321
13818
  findFailedListenersForSourceRun,
13322
13819
  replayFailedListenerRun,
13820
+ VERSION_MANIFEST,
13821
+ LATEST_VERSION,
13822
+ getVersionPolicy,
13823
+ isVersionAtLeast,
13323
13824
  reconcilePendingInvocations,
13324
13825
  getActionForBlock,
13325
13826
  writeRunRecordAndReconcile,
@@ -13328,12 +13829,16 @@ export {
13328
13829
  createRuntimeStateManager,
13329
13830
  clearRuntimeForTemplateClone,
13330
13831
  resolveRuntimeRefs,
13331
- LATEST_VERSION,
13332
13832
  isAuthorized,
13333
13833
  executeNode,
13834
+ PROOF_MISSING_CODE,
13835
+ validateActionProof,
13334
13836
  reconcileActionReadBack,
13335
13837
  buildActionRunInputs,
13336
13838
  executeActionBlock,
13839
+ verifyCompletion,
13840
+ isVerifiedCompletion,
13841
+ validateBlockConfig,
13337
13842
  createUcanDelegationStore,
13338
13843
  createMemoryUcanDelegationStore,
13339
13844
  createInvocationStore,
@@ -13389,4 +13894,4 @@ export {
13389
13894
  executeQueuedFlowAgentCoreCommands,
13390
13895
  FlowAgentService
13391
13896
  };
13392
- //# sourceMappingURL=chunk-KNMPGX5G.js.map
13897
+ //# sourceMappingURL=chunk-5TJK6JKV.js.map