lua-cli 3.31.0 → 3.32.1

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.
@@ -1348,7 +1348,6 @@ function luaClientMetricLabels(client) {
1348
1348
  const canonical2 = parseLuaClientHeader(serializeLuaClientHeader(client));
1349
1349
  return {
1350
1350
  client_family: canonical2?.app ?? "unknown",
1351
- client_version: canonical2?.version ?? "unknown",
1352
1351
  client_attribution: canonical2?.attribution ?? "unknown"
1353
1352
  };
1354
1353
  }
@@ -1528,6 +1527,27 @@ var WORKFLOW_RUN_STATUSES = [
1528
1527
  ...WORKFLOW_RUN_IDLE,
1529
1528
  ...WORKFLOW_RUN_TERMINAL
1530
1529
  ];
1530
+ var WORKFLOW_STEP_STATUSES = [
1531
+ "pending",
1532
+ "ready",
1533
+ "waiting",
1534
+ "dispatched",
1535
+ "claimed",
1536
+ "running",
1537
+ "suspended",
1538
+ "cancellation_requested",
1539
+ "completed",
1540
+ "failed",
1541
+ "skipped",
1542
+ "cancelled",
1543
+ "timeout",
1544
+ "reaped"
1545
+ ];
1546
+ var WORKFLOW_STEP_IN_FLIGHT = [
1547
+ "claimed",
1548
+ "running",
1549
+ "cancellation_requested"
1550
+ ];
1531
1551
  var ARCHIVE_WINDOW_MARGIN_DAYS = 7;
1532
1552
  function shouldSkipArchive(run, manifestSha256, sinkSha256) {
1533
1553
  if (!run.completedAt || !run.exportedAt || run.exportedAt < run.completedAt) return false;
@@ -1588,6 +1608,7 @@ function workflowOrgSlotKey(orgId, kind) {
1588
1608
  }
1589
1609
  __name(workflowOrgSlotKey, "workflowOrgSlotKey");
1590
1610
  __name2(workflowOrgSlotKey, "workflowOrgSlotKey");
1611
+ var WORKFLOW_CONNECTION_KEY_RE = /^[a-z][a-z0-9_-]{0,63}$/;
1591
1612
  function scheduledTimeKey(scheduledTime) {
1592
1613
  return scheduledTime.replace(/[^0-9TZ]/g, "");
1593
1614
  }
@@ -1599,6 +1620,137 @@ function scheduledWorkflowRunIdForTime(jobId, scheduledTime) {
1599
1620
  __name(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1600
1621
  __name2(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1601
1622
  var WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES = 64 * 1024;
1623
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
1624
+ var PROVIDER_MESSAGE_MAX_CHARS = 300;
1625
+ var SECRET_LITERAL_PATTERNS = [
1626
+ {
1627
+ re: /\b(github_pat_)[A-Za-z0-9_]{16,}/g
1628
+ },
1629
+ {
1630
+ re: /\b(gh[pousr]_)[A-Za-z0-9]{16,}/g
1631
+ },
1632
+ {
1633
+ re: /\b(glpat-)[A-Za-z0-9_-]{16,}/g
1634
+ },
1635
+ {
1636
+ re: /\b(sk-ant-)[A-Za-z0-9_-]{16,}/g
1637
+ },
1638
+ {
1639
+ re: /\b(sk-)(?!ant-)[A-Za-z0-9_-]{20,}/g
1640
+ },
1641
+ {
1642
+ re: /\b(AKIA)[A-Z0-9]{16}\b/g
1643
+ },
1644
+ {
1645
+ re: /\b(xox[abprs]-)[A-Za-z0-9-]{10,}/g
1646
+ },
1647
+ {
1648
+ re: /\b(AIza)[0-9A-Za-z_-]{35}/g
1649
+ },
1650
+ {
1651
+ re: /\b(ya29\.)[A-Za-z0-9_-]{20,}/g
1652
+ },
1653
+ {
1654
+ re: /\b(eyJ)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
1655
+ },
1656
+ {
1657
+ re: /(-----BEGIN [A-Z ]*PRIVATE KEY-----)[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
1658
+ },
1659
+ // `https://x-access-token:<token>@github.com/…` (the git credential shape the pod scrubbed already)
1660
+ {
1661
+ re: /\b(x-access-token:)[^@\s]+(@)/g,
1662
+ suffix: true
1663
+ }
1664
+ ];
1665
+ var SECRET_NAME = "(?:authorization|proxy-authorization|x-api-key|x-wf-[a-z-]*key|x-internal-auth|api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|secret|password|passwd|pwd|passphrase|token|credentials?)";
1666
+ var SECRET_PAIR_PATTERNS = [
1667
+ // `Authorization: Bearer x`, `x-api-key: x`, `AWS_SECRET_ACCESS_KEY=x`, `SVC_JOB_KEY=x`, `"apiKey": "x"`,
1668
+ // `FOO_TOKEN=x`, `secretKey=x`, `password=x`. Groups: the char before the name, the name, the separator
1669
+ // (with its quotes), the scheme word — all kept; the value goes. A value a literal rule already replaced
1670
+ // (`x-access-token:[REDACTED]@host`) is left alone so the host after it survives.
1671
+ {
1672
+ re: new RegExp(`(^|[^A-Za-z0-9])((?:[A-Za-z0-9-]+[_-])?${SECRET_NAME}|[A-Za-z0-9-]+_key)(["']?\\s*[:=]\\s*["']?)((?:basic\\s+|bearer\\s+|token\\s+)?)(?!\\[REDACTED\\])[^\\s"',;)}&]{4,}`, "gi")
1673
+ },
1674
+ // `?token=x`, `&key=x`, `&X-Amz-Signature=x`, `&sig=x`
1675
+ {
1676
+ re: /([?&](?:token|key|api[_-]?key|apikey|access[_-]?token|id[_-]?token|auth|sig|signature|secret|password|pwd|x-amz-signature|x-amz-credential|x-amz-security-token)=)[^&\s"'#]+/gi
1677
+ },
1678
+ // `mongodb+srv://user:pass@host`, `postgres://user:pass@host`, `https://user:pass@host`
1679
+ {
1680
+ re: /(\/\/[^\s/:@]+:)[^\s/@]+(@)/g,
1681
+ suffix: true
1682
+ },
1683
+ // `Basic <base64>` / `bearer <short token>` (the literal list needs ≥ 16 chars; any case)
1684
+ {
1685
+ re: /\b((?:Basic|Bearer)\s+)(?!\[REDACTED\])[A-Za-z0-9+/=_.-]{8,}/gi
1686
+ }
1687
+ ];
1688
+ var GROUP_COUNT = /* @__PURE__ */ new WeakMap();
1689
+ function groupCount(re) {
1690
+ let n2 = GROUP_COUNT.get(re);
1691
+ if (n2 === void 0) {
1692
+ n2 = new RegExp(`${re.source}|`, re.flags.replace("g", "")).exec("").length - 1;
1693
+ GROUP_COUNT.set(re, n2);
1694
+ }
1695
+ return n2;
1696
+ }
1697
+ __name(groupCount, "groupCount");
1698
+ __name2(groupCount, "groupCount");
1699
+ function applyPatterns(text, patterns) {
1700
+ let out = text;
1701
+ for (const { re, suffix } of patterns) {
1702
+ re.lastIndex = 0;
1703
+ if (!re.test(out)) continue;
1704
+ re.lastIndex = 0;
1705
+ const groups = groupCount(re);
1706
+ out = out.replace(re, (...args) => {
1707
+ const kept = args.slice(1, 1 + groups).map((g) => typeof g === "string" ? g : "");
1708
+ if (suffix && kept.length > 0) {
1709
+ const tail = kept[kept.length - 1];
1710
+ return `${kept.slice(0, -1).join("")}${REDACTED_PLACEHOLDER}${tail}`;
1711
+ }
1712
+ return `${kept.join("")}${REDACTED_PLACEHOLDER}`;
1713
+ });
1714
+ }
1715
+ return out;
1716
+ }
1717
+ __name(applyPatterns, "applyPatterns");
1718
+ __name2(applyPatterns, "applyPatterns");
1719
+ function scrubSecretText(text) {
1720
+ if (typeof text !== "string" || text.length < 4) return text;
1721
+ return applyPatterns(applyPatterns(text, SECRET_LITERAL_PATTERNS), SECRET_PAIR_PATTERNS);
1722
+ }
1723
+ __name(scrubSecretText, "scrubSecretText");
1724
+ __name2(scrubSecretText, "scrubSecretText");
1725
+ function scrubSecretLines(lines) {
1726
+ return lines.map((l) => typeof l === "string" ? scrubSecretText(l) : l);
1727
+ }
1728
+ __name(scrubSecretLines, "scrubSecretLines");
1729
+ __name2(scrubSecretLines, "scrubSecretLines");
1730
+ function messageText(value3) {
1731
+ if (typeof value3 === "string") return value3;
1732
+ if (value3 instanceof Error) return value3.message;
1733
+ if (value3 && typeof value3 === "object") {
1734
+ const m = value3.message;
1735
+ if (typeof m === "string") return m;
1736
+ try {
1737
+ return JSON.stringify(value3);
1738
+ } catch {
1739
+ return "";
1740
+ }
1741
+ }
1742
+ return value3 === void 0 || value3 === null ? "" : String(value3);
1743
+ }
1744
+ __name(messageText, "messageText");
1745
+ __name2(messageText, "messageText");
1746
+ function scrubProviderMessage(raw, max = PROVIDER_MESSAGE_MAX_CHARS) {
1747
+ const text = messageText(raw).replace(/\s+/g, " ").trim();
1748
+ if (!text) return void 0;
1749
+ const out = scrubSecretText(text);
1750
+ return out.length > max ? `${out.slice(0, max - 1)}\u2026` : out;
1751
+ }
1752
+ __name(scrubProviderMessage, "scrubProviderMessage");
1753
+ __name2(scrubProviderMessage, "scrubProviderMessage");
1602
1754
  var WORKFLOW_AUDIT_EVENTS = [
1603
1755
  // --- definitions / versions / templates (13, 11 §11.11.5) ---
1604
1756
  "workflow.published",
@@ -1811,10 +1963,31 @@ __name2(renderTargetsBlock, "renderTargetsBlock");
1811
1963
 
1812
1964
  // ../workflow-graph/dist/index.mjs
1813
1965
  import { z as z4 } from "zod";
1966
+ import { z as z22 } from "zod";
1814
1967
  import { createHash as createHash2 } from "crypto";
1815
1968
  var __defProp3 = Object.defineProperty;
1816
1969
  var __name3 = /* @__PURE__ */ __name((target, value22) => __defProp3(target, "name", { value: value22, configurable: true }), "__name");
1970
+ var WORKFLOW_SIDE_EFFECTS = [
1971
+ "none",
1972
+ "external"
1973
+ ];
1974
+ var WORKFLOW_JOB_RESOURCES = [
1975
+ "small",
1976
+ "medium",
1977
+ "large"
1978
+ ];
1979
+ var SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
1980
+ var JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
1817
1981
  var WORKFLOW_ARM_SUBRUN_ID = "$arm";
1982
+ var SLEEP_UNTIL_REPLACEMENT = Object.freeze({
1983
+ type: "sleep",
1984
+ duration: 6e4
1985
+ });
1986
+ function sleepUntilUnsupportedMessage(id) {
1987
+ return `the engine does not execute \`sleepUntil\` yet (node "${id}") \u2014 replace it with a \`sleep\` node with a \`duration\` in ms, e.g. { type: 'sleep', id: '${id}', duration: ${SLEEP_UNTIL_REPLACEMENT.duration} }`;
1988
+ }
1989
+ __name(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
1990
+ __name3(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
1818
1991
  var WORKFLOW_CAPS_DEFAULT = Object.freeze({
1819
1992
  maxParallelArms: 16,
1820
1993
  maxForeachConcurrency: 16,
@@ -1944,6 +2117,17 @@ function withDefaultsFilled(g) {
1944
2117
  }
1945
2118
  __name(withDefaultsFilled, "withDefaultsFilled");
1946
2119
  __name3(withDefaultsFilled, "withDefaultsFilled");
2120
+ var CONNECTION_ID_HEX_RE = /^[0-9a-f]{24}$/;
2121
+ function isConnectionKeyShaped(value22) {
2122
+ return WORKFLOW_CONNECTION_KEY_RE.test(value22) && !CONNECTION_ID_HEX_RE.test(value22);
2123
+ }
2124
+ __name(isConnectionKeyShaped, "isConnectionKeyShaped");
2125
+ __name3(isConnectionKeyShaped, "isConnectionKeyShaped");
2126
+ function connectionKeyUndeclaredMessage(path, key) {
2127
+ return `${path} '${key}' is neither a connection id nor a declared connections[].key \u2014 declare it: connections: [{ key: '${key}', integrationType: '<catalog slug, e.g. github>' }] and it resolves on any agent`;
2128
+ }
2129
+ __name(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
2130
+ __name3(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
1947
2131
  var WORKFLOW_JOB_TOOLS = [
1948
2132
  "shell",
1949
2133
  "read",
@@ -2087,6 +2271,33 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2087
2271
  if (envelopeWorkspace?.backend && envelopeWorkspace.backend !== "ebs" && opts.policy?.workspaceBackends && !opts.policy.workspaceBackends.includes(envelopeWorkspace.backend)) {
2088
2272
  err("workspace-backend-unavailable", `workspace.backend '${envelopeWorkspace.backend}' is not enabled (LUA_WF_WORKSPACE_BACKENDS = ${opts.policy.workspaceBackends.join(",")}) \u2014 'ebs' is the normative fallback`, "workspace.backend");
2089
2273
  }
2274
+ const declaredKeys = new Set(opts.connectionKeys ?? []);
2275
+ if (g.connections !== void 0 && !Array.isArray(g.connections)) {
2276
+ err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
2277
+ }
2278
+ (Array.isArray(g.connections) ? g.connections : []).forEach((c, i) => {
2279
+ const path = `connections.${i}`;
2280
+ const key = c?.key;
2281
+ const integrationType = c?.integrationType;
2282
+ if (typeof key !== "string" || !WORKFLOW_CONNECTION_KEY_RE.test(key)) {
2283
+ err("connection-declaration-invalid", `connections[${i}].key must match ${WORKFLOW_CONNECTION_KEY_RE}`, `${path}.key`);
2284
+ return;
2285
+ }
2286
+ if (declaredKeys.has(key)) {
2287
+ err("connection-declaration-invalid", `connections[${i}].key "${key}" is declared twice`, `${path}.key`);
2288
+ return;
2289
+ }
2290
+ if (typeof integrationType !== "string" || !integrationType.trim()) {
2291
+ err("connection-declaration-invalid", `connections[${i}] ("${key}") needs an integrationType (the catalog slug, e.g. 'github')`, `${path}.integrationType`);
2292
+ return;
2293
+ }
2294
+ declaredKeys.add(key);
2295
+ });
2296
+ const undeclaredKey = /* @__PURE__ */ __name3((ref) => typeof ref === "string" && !declaredKeys.has(ref) && isConnectionKeyShaped(ref) && opts.connectionIds?.has(ref) !== true, "undeclaredKey");
2297
+ const credentialsRef = envelopeWorkspace?.credentialsRef;
2298
+ if (undeclaredKey(credentialsRef)) {
2299
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage("workspace.credentialsRef", credentialsRef), "workspace.credentialsRef");
2300
+ }
2090
2301
  const seen = /* @__PURE__ */ new Map();
2091
2302
  let nodeCount = 0;
2092
2303
  const upstream = /* @__PURE__ */ new Set();
@@ -2098,6 +2309,16 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2098
2309
  seen.set(id, path);
2099
2310
  }
2100
2311
  }, "checkId");
2312
+ const checkPolicyEnums = /* @__PURE__ */ __name3((node, path) => {
2313
+ const id = singleId(node);
2314
+ const check = /* @__PURE__ */ __name3((member, allowed) => {
2315
+ const value22 = node[member];
2316
+ if (value22 === void 0 || typeof value22 === "string" && allowed.includes(value22)) return;
2317
+ err("invalid-envelope", `\`${member}\` must be ${allowed.map((a) => `'${a}'`).join(" | ")} (got ${JSON.stringify(value22)})`, `${path}.${member}`, id);
2318
+ }, "check");
2319
+ check("sideEffects", WORKFLOW_SIDE_EFFECTS);
2320
+ check("jobResources", WORKFLOW_JOB_RESOURCES);
2321
+ }, "checkPolicyEnums");
2101
2322
  const checkRetry = /* @__PURE__ */ __name3((node, path) => {
2102
2323
  const r = node.retry;
2103
2324
  if (!r) return;
@@ -2105,6 +2326,9 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2105
2326
  if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") {
2106
2327
  err("backoff-invalid", `retry.backoff must be 'fixed' | 'exponential'`, `${path}.retry.backoff`, id);
2107
2328
  }
2329
+ if (r.backoffSeconds !== void 0 && r.backoffSeconds < 0) {
2330
+ err("backoff-invalid", "retry.backoffSeconds must be \u2265 0", `${path}.retry.backoffSeconds`, id);
2331
+ }
2108
2332
  if (r.maxBackoffSeconds !== void 0) {
2109
2333
  if (r.backoff !== "exponential") {
2110
2334
  err("backoff-invalid", "retry.maxBackoffSeconds is only meaningful with backoff:'exponential'", `${path}.retry`, id);
@@ -2166,10 +2390,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2166
2390
  }, "checkSpecialistRole");
2167
2391
  const checkRequiredConnections = /* @__PURE__ */ __name3((node, path) => {
2168
2392
  const required = node.requiredConnections;
2169
- if (!Array.isArray(required) || !opts.connectionIds) return;
2170
- const unknown = required.filter((c) => typeof c !== "string" || !opts.connectionIds.has(c));
2393
+ if (!Array.isArray(required)) return;
2394
+ const undeclared = required.filter(undeclaredKey);
2395
+ if (undeclared.length) {
2396
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage(`${path}.requiredConnections`, undeclared[0]) + (undeclared.length > 1 ? ` (also undeclared: ${JSON.stringify(undeclared.slice(1))})` : ""), `${path}.requiredConnections`, singleId(node));
2397
+ }
2398
+ if (!opts.connectionIds) return;
2399
+ const unknown = required.filter((c) => typeof c !== "string" || !declaredKeys.has(c) && !opts.connectionIds.has(c));
2171
2400
  if (unknown.length) {
2172
- err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are not connections the owner can mount`, `${path}.requiredConnections`, singleId(node));
2401
+ err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are neither declared connections[].key values nor connections the owner can mount`, `${path}.requiredConnections`, singleId(node));
2173
2402
  }
2174
2403
  }, "checkRequiredConnections");
2175
2404
  const checkTier = /* @__PURE__ */ __name3((node, path) => {
@@ -2255,6 +2484,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2255
2484
  return;
2256
2485
  }
2257
2486
  checkId(singleId(node), path);
2487
+ checkPolicyEnums(node, path);
2258
2488
  checkTimeout(node, path);
2259
2489
  checkTier(node, path);
2260
2490
  checkRetry(node, path);
@@ -2321,6 +2551,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2321
2551
  case "sleepUntil": {
2322
2552
  const s = entry;
2323
2553
  checkId(s.id, path);
2554
+ err("node-type-unsupported-by-engine", sleepUntilUnsupportedMessage(s.id), path, s.id);
2324
2555
  if (s.date === void 0 === (s.dateFrom === void 0)) {
2325
2556
  err("invalid-envelope", "sleepUntil needs exactly one of `date` | `dateFrom`", path, s.id);
2326
2557
  }
@@ -2366,7 +2597,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2366
2597
  err("invalid-envelope", "conditional.predicates must match conditional.steps one-to-one", path);
2367
2598
  }
2368
2599
  (c.predicates ?? []).forEach((p, j) => {
2369
- if (!isPredicate(p)) err("closure-predicate", "a conditional predicate must be a LuaPredicate object, not a function", `${path}.predicates.${j}`);
2600
+ if (!isWellFormedPredicate(p)) err("closure-predicate", "a conditional predicate must be a well-formed LuaPredicate object ({op, left/right | value | path | args | arg}), not a function or an expression string", `${path}.predicates.${j}`);
2370
2601
  });
2371
2602
  c.steps.forEach((arm, j) => {
2372
2603
  checkArm(arm, `${path}.steps.${j}`, 1);
@@ -2446,7 +2677,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2446
2677
  if (l.intervalSeconds !== void 0 && (!Number.isInteger(l.intervalSeconds) || l.intervalSeconds < 1 || l.intervalSeconds > caps.maxLoopIntervalSeconds)) {
2447
2678
  err("loop-interval-out-of-range", `loop.intervalSeconds must be an integer in 1..${caps.maxLoopIntervalSeconds}`, `${path}.intervalSeconds`);
2448
2679
  }
2449
- if (!isPredicate(l.predicate)) err("closure-predicate", "a loop predicate must be a LuaPredicate object, not a function", `${path}.predicate`);
2680
+ if (!isWellFormedPredicate(l.predicate)) err("closure-predicate", "a loop predicate must be a well-formed LuaPredicate object ({op, left/right | value | path | args | arg}), not a function or an expression string", `${path}.predicate`);
2450
2681
  const bodyType = l.step.type;
2451
2682
  if (bodyType === "mapping") err("container-arm-empty", "a loop body needs a step, not a bare mapping", `${path}.step`);
2452
2683
  else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
@@ -2531,6 +2762,46 @@ function isPredicate(p) {
2531
2762
  }
2532
2763
  __name(isPredicate, "isPredicate");
2533
2764
  __name3(isPredicate, "isPredicate");
2765
+ var isPredicateScalar = /* @__PURE__ */ __name3((v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean", "isPredicateScalar");
2766
+ function isPathOrLiteral(v) {
2767
+ if (typeof v !== "object" || v === null) return false;
2768
+ const r = v;
2769
+ if ("path" in r) return typeof r.path === "string" && r.path.length > 0;
2770
+ return "literal" in r && isPredicateScalar(r.literal);
2771
+ }
2772
+ __name(isPathOrLiteral, "isPathOrLiteral");
2773
+ __name3(isPathOrLiteral, "isPathOrLiteral");
2774
+ function isWellFormedPredicate(p) {
2775
+ if (!isPredicate(p)) return false;
2776
+ const r = p;
2777
+ switch (r.op) {
2778
+ case "eq":
2779
+ case "ne":
2780
+ case "lt":
2781
+ case "lte":
2782
+ case "gt":
2783
+ case "gte":
2784
+ return isPathOrLiteral(r.left) && isPathOrLiteral(r.right);
2785
+ case "in":
2786
+ case "notIn":
2787
+ return isPathOrLiteral(r.value) && Array.isArray(r.set) && r.set.every(isPredicateScalar);
2788
+ case "exists":
2789
+ case "notExists":
2790
+ return typeof r.path === "string" && r.path.length > 0;
2791
+ case "truthy":
2792
+ case "falsy":
2793
+ return isPathOrLiteral(r.value);
2794
+ case "and":
2795
+ case "or":
2796
+ return Array.isArray(r.args) && r.args.every(isWellFormedPredicate);
2797
+ case "not":
2798
+ return isWellFormedPredicate(r.arg);
2799
+ default:
2800
+ return false;
2801
+ }
2802
+ }
2803
+ __name(isWellFormedPredicate, "isWellFormedPredicate");
2804
+ __name3(isWellFormedPredicate, "isWellFormedPredicate");
2534
2805
  var GRAPH_HASH_PREFIX = "sha256-cj1:";
2535
2806
  function canonicalJson(value22) {
2536
2807
  const seen = /* @__PURE__ */ new WeakSet();
@@ -3281,6 +3552,76 @@ var rows = /* @__PURE__ */ __name3((s, path, page) => ({
3281
3552
  var fromKnowledge = /* @__PURE__ */ __name3((k) => ({
3282
3553
  knowledge: k
3283
3554
  }), "fromKnowledge");
3555
+ var CONTINUED_FAILURE_TAG = "continued_failure";
3556
+ var CONTINUED_FAILURE_DEFAULT_CODE = "step_failed";
3557
+ var CONTINUED_FAILURE_OUTPUT_SCHEMA = Object.freeze({
3558
+ type: "object",
3559
+ properties: {
3560
+ __lua_workflow: {
3561
+ type: "string",
3562
+ const: CONTINUED_FAILURE_TAG
3563
+ },
3564
+ failed: {
3565
+ type: "boolean",
3566
+ const: true
3567
+ },
3568
+ error: {
3569
+ type: "object",
3570
+ properties: {
3571
+ code: {
3572
+ type: "string"
3573
+ },
3574
+ message: {
3575
+ type: "string"
3576
+ }
3577
+ },
3578
+ required: [
3579
+ "code",
3580
+ "message"
3581
+ ]
3582
+ },
3583
+ text: {
3584
+ type: "string",
3585
+ const: ""
3586
+ }
3587
+ },
3588
+ required: [
3589
+ "__lua_workflow",
3590
+ "failed",
3591
+ "error",
3592
+ "text"
3593
+ ]
3594
+ });
3595
+ var CONTINUED_FAILURE_LEAF_PATHS = Object.freeze([
3596
+ "failed",
3597
+ "error",
3598
+ "error.code",
3599
+ "error.message",
3600
+ "text"
3601
+ ]);
3602
+ function continuedFailureValue(error, killReason) {
3603
+ const code = typeof error?.code === "string" && error.code || typeof killReason === "string" && killReason || CONTINUED_FAILURE_DEFAULT_CODE;
3604
+ const message = typeof error?.message === "string" && error.message ? error.message : code;
3605
+ return {
3606
+ __lua_workflow: CONTINUED_FAILURE_TAG,
3607
+ failed: true,
3608
+ error: {
3609
+ code,
3610
+ message
3611
+ },
3612
+ text: ""
3613
+ };
3614
+ }
3615
+ __name(continuedFailureValue, "continuedFailureValue");
3616
+ __name3(continuedFailureValue, "continuedFailureValue");
3617
+ function isContinuedFailureValue(v) {
3618
+ if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
3619
+ const o = v;
3620
+ const err = o.error;
3621
+ return o.__lua_workflow === CONTINUED_FAILURE_TAG && o.failed === true && o.text === "" && err !== null && typeof err === "object" && typeof err.code === "string" && typeof err.message === "string";
3622
+ }
3623
+ __name(isContinuedFailureValue, "isContinuedFailureValue");
3624
+ __name3(isContinuedFailureValue, "isContinuedFailureValue");
3284
3625
  function lowerContainerArm(mapping, step22) {
3285
3626
  const stepId = nodeIdOf(step22);
3286
3627
  return {
@@ -3296,7 +3637,7 @@ function lowerContainerArm(mapping, step22) {
3296
3637
  }
3297
3638
  __name(lowerContainerArm, "lowerContainerArm");
3298
3639
  __name3(lowerContainerArm, "lowerContainerArm");
3299
- var nodeIdOf = /* @__PURE__ */ __name3((n) => n.type === "step" ? n.step.id : n.id, "nodeIdOf");
3640
+ var nodeIdOf = /* @__PURE__ */ __name3((n2) => n2.type === "step" ? n2.step.id : n2.id, "nodeIdOf");
3300
3641
  function entryIds(entry) {
3301
3642
  switch (entry.type) {
3302
3643
  case "parallel":
@@ -3383,7 +3724,7 @@ function resolvePlacements(calls) {
3383
3724
  if (!d) {
3384
3725
  issues.push({
3385
3726
  code: "unknown-step-ref",
3386
- message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })`,
3727
+ message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)`,
3387
3728
  callIndex: i,
3388
3729
  stepId: ref.ref
3389
3730
  });
@@ -3435,9 +3776,9 @@ function resolvePlacements(calls) {
3435
3776
  });
3436
3777
  const graph = [];
3437
3778
  const lookup = /* @__PURE__ */ __name3((ref) => {
3438
- const n = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3439
- if (!n || !ref.armMap || n.type === "mapping") return n;
3440
- return lowerContainerArm(ref.armMap, n);
3779
+ const n2 = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3780
+ if (!n2 || !ref.armMap || n2.type === "mapping") return n2;
3781
+ return lowerContainerArm(ref.armMap, n2);
3441
3782
  }, "lookup");
3442
3783
  calls.forEach((call, i) => {
3443
3784
  switch (call.kind) {
@@ -3456,7 +3797,7 @@ function resolvePlacements(calls) {
3456
3797
  return;
3457
3798
  }
3458
3799
  case "parallel": {
3459
- const steps = call.arms.map(lookup).filter((n) => !!n && n.type !== "mapping");
3800
+ const steps = call.arms.map(lookup).filter((n2) => !!n2 && n2.type !== "mapping");
3460
3801
  const node = {
3461
3802
  type: "parallel",
3462
3803
  steps
@@ -3469,9 +3810,9 @@ function resolvePlacements(calls) {
3469
3810
  const steps = [];
3470
3811
  const predicates = [];
3471
3812
  for (const a of call.arms) {
3472
- const n = lookup(a.target);
3473
- if (!n) continue;
3474
- steps.push(n);
3813
+ const n2 = lookup(a.target);
3814
+ if (!n2) continue;
3815
+ steps.push(n2);
3475
3816
  predicates.push(a.predicate);
3476
3817
  }
3477
3818
  const node = {
@@ -3579,7 +3920,35 @@ function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3579
3920
  }
3580
3921
  __name(seedLedgerFromRun, "seedLedgerFromRun");
3581
3922
  __name3(seedLedgerFromRun, "seedLedgerFromRun");
3582
- var armId2 = /* @__PURE__ */ __name3((arm) => arm.type === "step" ? arm.step.id : arm.id, "armId");
3923
+ var branchArmId = /* @__PURE__ */ __name3((arm) => arm.type === "step" ? arm.step.id : arm.id, "branchArmId");
3924
+ function branchSpecFromConditional(entry) {
3925
+ return {
3926
+ arms: entry.steps.map((arm, i) => ({
3927
+ stepId: branchArmId(arm),
3928
+ predicate: entry.predicates[i]
3929
+ })),
3930
+ ...entry.exclusive === true ? {
3931
+ exclusive: true
3932
+ } : {},
3933
+ ...entry.otherwise ? {
3934
+ otherwise: branchArmId(entry.otherwise)
3935
+ } : {}
3936
+ };
3937
+ }
3938
+ __name(branchSpecFromConditional, "branchSpecFromConditional");
3939
+ __name3(branchSpecFromConditional, "branchSpecFromConditional");
3940
+ function selectBranchArms(spec, ctx) {
3941
+ const taken = [];
3942
+ for (const arm of spec.arms) {
3943
+ if (spec.exclusive && taken.length > 0) break;
3944
+ const hit = arm.predicate ? evaluatePredicate(arm.predicate, ctx) : false;
3945
+ if (hit) taken.push(arm.stepId);
3946
+ }
3947
+ if (taken.length === 0 && spec.otherwise) taken.push(spec.otherwise);
3948
+ return taken;
3949
+ }
3950
+ __name(selectBranchArms, "selectBranchArms");
3951
+ __name3(selectBranchArms, "selectBranchArms");
3583
3952
  var canonical = /* @__PURE__ */ __name3((v) => JSON.stringify(sortKeys(v)), "canonical");
3584
3953
  var sortKeys = /* @__PURE__ */ __name3((v) => {
3585
3954
  if (Array.isArray(v)) return v.map(sortKeys);
@@ -3597,25 +3966,24 @@ function replayLedger(g, ledger) {
3597
3966
  r.stepId,
3598
3967
  r
3599
3968
  ]));
3600
- const stepResults = {};
3601
- for (const r of ledger.steps) if (r.status === "completed") stepResults[r.stepId] = r.output;
3602
- const ctx = {
3969
+ const requestContext = {
3970
+ runId: "replay",
3971
+ workflowId: g.definition.id,
3972
+ orgId: "",
3973
+ agentId: "",
3974
+ userId: "",
3975
+ trigger: "sdk",
3976
+ threadId: "replay",
3977
+ depth: 0,
3978
+ startedAt: 0,
3979
+ ...ledger.requestContext
3980
+ };
3981
+ const ctxFor = /* @__PURE__ */ __name3((id) => ({
3603
3982
  initData: ledger.initData,
3604
- stepResults,
3983
+ stepResults: ancestorResults(plan, id, rows22),
3605
3984
  state: ledger.state ?? {},
3606
- requestContext: {
3607
- runId: "replay",
3608
- workflowId: g.definition.id,
3609
- orgId: "",
3610
- agentId: "",
3611
- userId: "",
3612
- trigger: "sdk",
3613
- threadId: "replay",
3614
- depth: 0,
3615
- startedAt: 0,
3616
- ...ledger.requestContext
3617
- }
3618
- };
3985
+ requestContext
3986
+ }), "ctxFor");
3619
3987
  const verdicts = [];
3620
3988
  for (const id of plan.order) {
3621
3989
  const node = plan.steps[id];
@@ -3623,17 +3991,7 @@ function replayLedger(g, ledger) {
3623
3991
  if (!recorded || recorded.status === "pending" || recorded.status === "skipped") continue;
3624
3992
  if (node.kind === "branch") {
3625
3993
  const entry = node.entry;
3626
- const taken = [];
3627
- entry.steps.forEach((arm, i) => {
3628
- const pred = entry.predicates[i];
3629
- const hit = pred ? evaluatePredicate(pred, {
3630
- initData: ledger.initData,
3631
- stepResults,
3632
- state: ledger.state
3633
- }) : false;
3634
- if (hit && (!entry.exclusive || taken.length === 0)) taken.push(armId2(arm));
3635
- });
3636
- if (taken.length === 0 && entry.otherwise) taken.push(armId2(entry.otherwise));
3994
+ const taken = selectBranchArms(branchSpecFromConditional(entry), ctxFor(id));
3637
3995
  const recordedTaken = recorded.taken ?? inferTaken(entry, rows22);
3638
3996
  verdicts.push({
3639
3997
  stepId: id,
@@ -3644,7 +4002,7 @@ function replayLedger(g, ledger) {
3644
4002
  });
3645
4003
  } else if (node.kind === "map" && !id.endsWith(".join") && recorded.status === "completed") {
3646
4004
  const entry = node.entry;
3647
- const resolved = resolveMapping(parseMapConfig(entry.mapConfig, id), ctx);
4005
+ const resolved = resolveMapping(parseMapConfig(entry.mapConfig, id), ctxFor(id));
3648
4006
  const local = "error" in resolved ? {
3649
4007
  error: resolved.error,
3650
4008
  key: resolved.key
@@ -3659,7 +4017,7 @@ function replayLedger(g, ledger) {
3659
4017
  } else if (node.kind === "foreach") {
3660
4018
  const entry = node.entry;
3661
4019
  const source = node.dependsOn[0];
3662
- const items = source ? stepResults[source] : void 0;
4020
+ const items = source ? ctxFor(id).stepResults[source] : void 0;
3663
4021
  const local = Array.isArray(items) ? items.length : void 0;
3664
4022
  const recordedCount = recorded.itemCount ?? countChildren(entry, rows22);
3665
4023
  verdicts.push({
@@ -3678,6 +4036,77 @@ function replayLedger(g, ledger) {
3678
4036
  }
3679
4037
  __name(replayLedger, "replayLedger");
3680
4038
  __name3(replayLedger, "replayLedger");
4039
+ var JOIN = ".join";
4040
+ var entryOfJoin = /* @__PURE__ */ __name3((id) => id.endsWith(JOIN) ? id.slice(0, -JOIN.length) : void 0, "entryOfJoin");
4041
+ function replayResultOf(row, node) {
4042
+ if (!row) return void 0;
4043
+ if (row.status === "completed") return {
4044
+ value: row.output
4045
+ };
4046
+ if (row.status === "failed") {
4047
+ const onError = row.onError ?? node?.entry?.onError;
4048
+ if (onError === "continue") return {
4049
+ value: continuedFailureValue(row.error, row.killReason)
4050
+ };
4051
+ }
4052
+ return void 0;
4053
+ }
4054
+ __name(replayResultOf, "replayResultOf");
4055
+ __name3(replayResultOf, "replayResultOf");
4056
+ function ancestorResults(plan, id, rows22) {
4057
+ const out = {};
4058
+ const joinAliased = /* @__PURE__ */ new Set();
4059
+ const seen = /* @__PURE__ */ new Set();
4060
+ const take = /* @__PURE__ */ __name3((rowId) => {
4061
+ const hit = replayResultOf(rows22.get(rowId), plan.steps[rowId]);
4062
+ if (!hit) return void 0;
4063
+ if (!joinAliased.has(rowId)) out[rowId] = hit.value;
4064
+ const entryId = entryOfJoin(rowId);
4065
+ if (entryId) {
4066
+ out[entryId] = hit.value;
4067
+ joinAliased.add(entryId);
4068
+ const entryNode = plan.steps[entryId];
4069
+ if (entryNode?.kind === "foreach") {
4070
+ const body = branchArmId(entryNode.entry.step);
4071
+ out[body] = hit.value;
4072
+ joinAliased.add(body);
4073
+ }
4074
+ }
4075
+ return hit;
4076
+ }, "take");
4077
+ const walk2 = /* @__PURE__ */ __name3((ids) => {
4078
+ for (const dep of ids) {
4079
+ if (seen.has(dep)) continue;
4080
+ seen.add(dep);
4081
+ take(dep);
4082
+ const node = plan.steps[dep] ?? plan.steps[entryOfJoin(dep) ?? ""];
4083
+ if (!node) continue;
4084
+ if (node.kind === "foreach") {
4085
+ const body = branchArmId(node.entry.step);
4086
+ for (const rowId of rows22.keys()) if (rowId.startsWith(`${body}[`)) take(rowId);
4087
+ } else if (node.kind === "loop") {
4088
+ const body = branchArmId(node.entry.step);
4089
+ let best = -1;
4090
+ for (const rowId of rows22.keys()) {
4091
+ if (!rowId.startsWith(`${body}#`)) continue;
4092
+ const n2 = Number(rowId.slice(body.length + 1));
4093
+ if (!Number.isInteger(n2)) continue;
4094
+ const hit = take(rowId);
4095
+ if (!hit) continue;
4096
+ if (n2 > best) {
4097
+ best = n2;
4098
+ out[body] = hit.value;
4099
+ }
4100
+ }
4101
+ }
4102
+ walk2(node.dependsOn);
4103
+ }
4104
+ }, "walk");
4105
+ walk2(plan.steps[id]?.dependsOn ?? []);
4106
+ return out;
4107
+ }
4108
+ __name(ancestorResults, "ancestorResults");
4109
+ __name3(ancestorResults, "ancestorResults");
3681
4110
  function inferTaken(entry, rows22) {
3682
4111
  const arms = [
3683
4112
  ...entry.steps,
@@ -3685,7 +4114,7 @@ function inferTaken(entry, rows22) {
3685
4114
  entry.otherwise
3686
4115
  ] : []
3687
4116
  ];
3688
- return arms.map(armId2).filter((id) => {
4117
+ return arms.map(branchArmId).filter((id) => {
3689
4118
  const r = rows22.get(id);
3690
4119
  return r !== void 0 && r.status !== "skipped" && r.status !== "pending";
3691
4120
  });
@@ -3693,10 +4122,10 @@ function inferTaken(entry, rows22) {
3693
4122
  __name(inferTaken, "inferTaken");
3694
4123
  __name3(inferTaken, "inferTaken");
3695
4124
  function countChildren(entry, rows22) {
3696
- const body = armId2(entry.step);
3697
- let n = 0;
3698
- for (const id of rows22.keys()) if (id.startsWith(`${body}[`)) n += 1;
3699
- return n;
4125
+ const body = branchArmId(entry.step);
4126
+ let n2 = 0;
4127
+ for (const id of rows22.keys()) if (id.startsWith(`${body}[`)) n2 += 1;
4128
+ return n2;
3700
4129
  }
3701
4130
  __name(countChildren, "countChildren");
3702
4131
  __name3(countChildren, "countChildren");
@@ -3713,21 +4142,58 @@ function pruneUndefined(o) {
3713
4142
  __name(pruneUndefined, "pruneUndefined");
3714
4143
  __name3(pruneUndefined, "pruneUndefined");
3715
4144
  function runNextAction(run) {
3716
- if (isTerminalRunStatus(run.status) || !run.cancel?.requestedAt) return "none";
4145
+ if (isTerminalRunStatus(run.status)) return "none";
4146
+ if (run.status === "suspended" && run.gate?.kind === "budget") return "raise_budget";
4147
+ if (!run.cancel?.requestedAt) return "none";
3717
4148
  const forceAt = run.cancel.forceAfter ?? run.cancel.requestedAt + FORCE_CANCEL_STALE_MS;
3718
4149
  return Date.now() >= forceAt ? "force" : "cancel_again";
3719
4150
  }
3720
4151
  __name(runNextAction, "runNextAction");
3721
4152
  __name3(runNextAction, "runNextAction");
4153
+ var IN_FLIGHT = new Set(WORKFLOW_STEP_IN_FLIGHT);
4154
+ function emptyRunCounts() {
4155
+ const out = {
4156
+ total: 0,
4157
+ inFlight: 0
4158
+ };
4159
+ for (const status of WORKFLOW_STEP_STATUSES) out[status] = 0;
4160
+ return out;
4161
+ }
4162
+ __name(emptyRunCounts, "emptyRunCounts");
4163
+ __name3(emptyRunCounts, "emptyRunCounts");
4164
+ function runCountsFromStatusTally(tally) {
4165
+ const out = emptyRunCounts();
4166
+ for (const [status, n2] of Object.entries(tally)) {
4167
+ if (!Number.isFinite(n2) || n2 <= 0) continue;
4168
+ out.total += n2;
4169
+ if (WORKFLOW_STEP_STATUSES.includes(status)) out[status] += n2;
4170
+ if (IN_FLIGHT.has(status)) out.inFlight += n2;
4171
+ }
4172
+ return out;
4173
+ }
4174
+ __name(runCountsFromStatusTally, "runCountsFromStatusTally");
4175
+ __name3(runCountsFromStatusTally, "runCountsFromStatusTally");
4176
+ function runCountsFromStepStatuses(statuses) {
4177
+ const tally = {};
4178
+ for (const s of statuses) tally[s] = (tally[s] ?? 0) + 1;
4179
+ return runCountsFromStatusTally(tally);
4180
+ }
4181
+ __name(runCountsFromStepStatuses, "runCountsFromStepStatuses");
4182
+ __name3(runCountsFromStepStatuses, "runCountsFromStepStatuses");
4183
+ var n = /* @__PURE__ */ __name3((v) => typeof v === "number" && Number.isFinite(v) ? v : 0, "n");
3722
4184
  function runCounts(counts) {
4185
+ const c = counts ?? {};
4186
+ const rawInFlight = c.dispatched !== void 0 || c.claimed !== void 0 || c.running !== void 0 || c.cancellation_requested !== void 0;
3723
4187
  return {
3724
- total: counts?.total ?? 0,
3725
- completed: counts?.completed ?? 0,
3726
- failed: counts?.failed ?? 0,
3727
- skipped: counts?.skipped ?? 0,
3728
- running: counts?.inFlight ?? 0,
3729
- suspended: counts?.suspended ?? 0,
3730
- pending: (counts?.pending ?? 0) + (counts?.ready ?? 0) + (counts?.waiting ?? 0)
4188
+ total: n(c.total),
4189
+ completed: n(c.completed),
4190
+ failed: n(c.failed) + n(c.timeout) + n(c.reaped),
4191
+ skipped: n(c.skipped),
4192
+ // LUA-664: cancelled rows never ran — their own wire bucket, never a failure (the detail's map, verbatim).
4193
+ cancelled: n(c.cancelled),
4194
+ running: rawInFlight ? n(c.dispatched) + n(c.claimed) + n(c.running) + n(c.cancellation_requested) : n(c.inFlight),
4195
+ suspended: n(c.suspended),
4196
+ pending: n(c.pending) + n(c.ready) + n(c.waiting)
3731
4197
  };
3732
4198
  }
3733
4199
  __name(runCounts, "runCounts");
@@ -3757,7 +4223,7 @@ function runWorkspaceView(ws) {
3757
4223
  if (!ws) return void 0;
3758
4224
  const w = ws;
3759
4225
  return pruneUndefined({
3760
- kind: w.kind ?? "empty",
4226
+ kind: w.spec?.kind ?? w.kind ?? "empty",
3761
4227
  backend: w.backend,
3762
4228
  status: String(w.status ?? ""),
3763
4229
  branch: w.branch,
@@ -3821,7 +4287,10 @@ function toWorkflowRunSummary(run) {
3821
4287
  eventSeq: run.eventSeq ?? 0,
3822
4288
  cancellable: !isTerminalRunStatus(status),
3823
4289
  nextAction: runNextAction(run),
3824
- workspace: runWorkspaceView(run.workspace)
4290
+ workspace: runWorkspaceView(run.workspace),
4291
+ // LUA-643: the runs list says a result exists; R4 `fields:'full'` (output-ACL gated, audited) serves it. The
4292
+ // stamped flag is what a list page carries (it projects `output` out); the payload members cover R4's full row.
4293
+ hasOutput: run.hasOutput === true || run.output !== void 0 || run.outputRef !== void 0 ? true : void 0
3825
4294
  });
3826
4295
  }
3827
4296
  __name(toWorkflowRunSummary, "toWorkflowRunSummary");
@@ -4382,54 +4851,54 @@ __name(rebaseItemPointer, "rebaseItemPointer");
4382
4851
  __name3(rebaseItemPointer, "rebaseItemPointer");
4383
4852
  var APPROVER_SPEC_MAX_USERS = 20;
4384
4853
  var ESCALATION_MAX_HOPS = 3;
4385
- var TemplateBindingSchema = z4.object({
4386
- template: z4.string().min(1).max(2048)
4854
+ var TemplateBindingSchema = z22.object({
4855
+ template: z22.string().min(1).max(2048)
4387
4856
  }).strict();
4388
- var ApproverSpecSchema = z4.union([
4389
- z4.literal("creator"),
4390
- z4.literal("org-admins"),
4391
- z4.object({
4392
- users: z4.union([
4393
- z4.array(z4.string().min(1).max(128)).min(1).max(APPROVER_SPEC_MAX_USERS),
4857
+ var ApproverSpecSchema = z22.union([
4858
+ z22.literal("creator"),
4859
+ z22.literal("org-admins"),
4860
+ z22.object({
4861
+ users: z22.union([
4862
+ z22.array(z22.string().min(1).max(128)).min(1).max(APPROVER_SPEC_MAX_USERS),
4394
4863
  TemplateBindingSchema
4395
4864
  ])
4396
4865
  }).strict(),
4397
- z4.object({
4398
- role: z4.union([
4399
- z4.string().min(1).max(128),
4866
+ z22.object({
4867
+ role: z22.union([
4868
+ z22.string().min(1).max(128),
4400
4869
  TemplateBindingSchema
4401
4870
  ])
4402
4871
  }).strict(),
4403
- z4.object({
4404
- group: z4.union([
4405
- z4.string().min(1).max(128),
4872
+ z22.object({
4873
+ group: z22.union([
4874
+ z22.string().min(1).max(128),
4406
4875
  TemplateBindingSchema
4407
4876
  ])
4408
4877
  }).strict(),
4409
- z4.object({
4410
- governance: z4.object({
4411
- policyId: z4.string().min(1).max(128)
4878
+ z22.object({
4879
+ governance: z22.object({
4880
+ policyId: z22.string().min(1).max(128)
4412
4881
  }).strict()
4413
4882
  }).strict()
4414
4883
  ]);
4415
- var FourEyesSchema = z4.object({
4884
+ var FourEyesSchema = z22.object({
4416
4885
  edit: ApproverSpecSchema,
4417
4886
  approve: ApproverSpecSchema
4418
4887
  }).strict();
4419
- var EscalationHopSchema = z4.object({
4888
+ var EscalationHopSchema = z22.object({
4420
4889
  escalateTo: ApproverSpecSchema,
4421
- timeoutHours: z4.number().finite().min(1).max(720)
4890
+ timeoutHours: z22.number().finite().min(1).max(720)
4422
4891
  }).strict();
4423
- var TerminalOutcomeSchema = z4.enum([
4892
+ var TerminalOutcomeSchema = z22.enum([
4424
4893
  "deny",
4425
4894
  "cancel-run",
4426
4895
  "fail",
4427
4896
  "continue"
4428
4897
  ]);
4429
- var ApprovalOnTimeoutSchema = z4.union([
4898
+ var ApprovalOnTimeoutSchema = z22.union([
4430
4899
  TerminalOutcomeSchema,
4431
4900
  EscalationHopSchema,
4432
- z4.array(z4.union([
4901
+ z22.array(z22.union([
4433
4902
  TerminalOutcomeSchema,
4434
4903
  EscalationHopSchema
4435
4904
  ])).min(1).max(ESCALATION_MAX_HOPS + 1)
@@ -4707,8 +5176,8 @@ __name(armEntry, "armEntry");
4707
5176
  __name3(armEntry, "armEntry");
4708
5177
  function ofEntry(e) {
4709
5178
  if (!e || typeof e !== "object") return ZERO;
4710
- const n = e;
4711
- switch (n.type) {
5179
+ const n2 = e;
5180
+ switch (n2.type) {
4712
5181
  case "agent":
4713
5182
  return {
4714
5183
  steps: {
@@ -4741,17 +5210,17 @@ function ofEntry(e) {
4741
5210
  agentCalls: 0
4742
5211
  };
4743
5212
  case "parallel": {
4744
- const arms = Array.isArray(n.steps) ? n.steps : [];
5213
+ const arms = Array.isArray(n2.steps) ? n2.steps : [];
4745
5214
  return arms.map(armEntry).map(ofEntry).reduce(add, ZERO);
4746
5215
  }
4747
5216
  case "conditional": {
4748
- const arms = (Array.isArray(n.steps) ? n.steps : []).map(armEntry).map(ofEntry);
5217
+ const arms = (Array.isArray(n2.steps) ? n2.steps : []).map(armEntry).map(ofEntry);
4749
5218
  if (arms.length === 0) return ZERO;
4750
- const hasOtherwise = n.otherwise !== void 0;
5219
+ const hasOtherwise = n2.otherwise !== void 0;
4751
5220
  const minArm = arms.reduce((a, b) => b.credits.min < a.credits.min ? b : a);
4752
5221
  const maxArm = arms.reduce((a, b) => b.credits.max > a.credits.max ? b : a);
4753
5222
  const summed = arms.reduce(add, ZERO);
4754
- const max = n.exclusive === true ? maxArm : summed;
5223
+ const max = n2.exclusive === true ? maxArm : summed;
4755
5224
  const min = hasOtherwise ? minArm : {
4756
5225
  ...ZERO
4757
5226
  };
@@ -4768,17 +5237,14 @@ function ofEntry(e) {
4768
5237
  };
4769
5238
  }
4770
5239
  case "foreach": {
4771
- const body = ofEntry(armEntry(n.step));
4772
- const opts = n.options ?? {};
4773
- const cap = typeof opts.maxItems === "number" && opts.maxItems > 0 ? opts.maxItems : 1;
5240
+ const body = ofEntry(armEntry(n2.step));
5241
+ const opts = n2.opts ?? {};
5242
+ const cap = typeof opts.maxItems === "number" && opts.maxItems > 0 ? opts.maxItems : WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
4774
5243
  return scale(body, 0, cap);
4775
5244
  }
4776
- case "loop":
4777
- case "dowhile":
4778
- case "dountil": {
4779
- const body = ofEntry(armEntry(n.step));
4780
- const opts = n.options ?? {};
4781
- const cap = typeof opts.maxIterations === "number" && opts.maxIterations > 0 ? opts.maxIterations : 1;
5245
+ case "loop": {
5246
+ const body = ofEntry(armEntry(n2.step));
5247
+ const cap = typeof n2.maxIterations === "number" && n2.maxIterations > 0 ? n2.maxIterations : WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
4782
5248
  return scale(body, 1, cap);
4783
5249
  }
4784
5250
  default:
@@ -4813,6 +5279,67 @@ function estimateGraph(envelopeOrGraph) {
4813
5279
  }
4814
5280
  __name(estimateGraph, "estimateGraph");
4815
5281
  __name3(estimateGraph, "estimateGraph");
5282
+ var isRecord2 = /* @__PURE__ */ __name3((v) => !!v && typeof v === "object" && !Array.isArray(v), "isRecord");
5283
+ function* singleStepsOf(entry) {
5284
+ if (!isRecord2(entry)) return;
5285
+ switch (entry.type) {
5286
+ case "step":
5287
+ case "agent":
5288
+ case "tool":
5289
+ yield entry;
5290
+ return;
5291
+ case "workflow":
5292
+ yield entry;
5293
+ if (Array.isArray(entry.graph)) yield* singleStepsOf(entry.graph[1]);
5294
+ return;
5295
+ case "parallel":
5296
+ case "conditional":
5297
+ if (Array.isArray(entry.steps)) for (const arm of entry.steps) yield* singleStepsOf(arm);
5298
+ yield* singleStepsOf(entry.otherwise);
5299
+ return;
5300
+ case "foreach":
5301
+ case "loop":
5302
+ yield* singleStepsOf(entry.step);
5303
+ return;
5304
+ default:
5305
+ return;
5306
+ }
5307
+ }
5308
+ __name(singleStepsOf, "singleStepsOf");
5309
+ __name3(singleStepsOf, "singleStepsOf");
5310
+ function entriesOf(graph) {
5311
+ const definition = isRecord2(graph) ? graph.definition : void 0;
5312
+ const entries = isRecord2(definition) ? definition.graph : void 0;
5313
+ return Array.isArray(entries) ? entries : [];
5314
+ }
5315
+ __name(entriesOf, "entriesOf");
5316
+ __name3(entriesOf, "entriesOf");
5317
+ function inheritTargets(graphs) {
5318
+ const targets = /* @__PURE__ */ new Set();
5319
+ for (const graph of graphs) {
5320
+ for (const entry of entriesOf(graph)) {
5321
+ for (const node of singleStepsOf(entry)) {
5322
+ if (node.type === "workflow" && node.workspace === "inherit" && typeof node.workflowId === "string" && node.workflowId.length > 0) {
5323
+ targets.add(node.workflowId);
5324
+ }
5325
+ }
5326
+ }
5327
+ }
5328
+ return targets;
5329
+ }
5330
+ __name(inheritTargets, "inheritTargets");
5331
+ __name3(inheritTargets, "inheritTargets");
5332
+ function needsInheritedWorkspace(graph) {
5333
+ if (!isRecord2(graph) || graph.workspace !== void 0) return false;
5334
+ for (const entry of entriesOf(graph)) {
5335
+ for (const node of singleStepsOf(entry)) {
5336
+ if (node.workspace !== void 0 && node.workspace !== "inherit") return true;
5337
+ }
5338
+ }
5339
+ return false;
5340
+ }
5341
+ __name(needsInheritedWorkspace, "needsInheritedWorkspace");
5342
+ __name3(needsInheritedWorkspace, "needsInheritedWorkspace");
4816
5343
 
4817
5344
  // src/types/workflow.ts
4818
5345
  function createStep(s) {
@@ -5008,7 +5535,7 @@ var LuaWorkflow = class {
5008
5535
  getEnvTemplateKeys() {
5009
5536
  return this.built.envTemplateKeys;
5010
5537
  }
5011
- /** `.workflow(id, ref)` targets by name — `ManifestWorkflow.workflowRefs`. */
5538
+ /** `.workflow(id, ref)` targets by name — `ManifestWorkflow.workflowRefs`; `workspace:'inherit'` marks the inherit children the compiler defers `workspace-not-declared` for. */
5012
5539
  getNestedWorkflowRefs() {
5013
5540
  return this.built.nestedRefs;
5014
5541
  }
@@ -5038,6 +5565,7 @@ var LuaWorkflow = class {
5038
5565
  };
5039
5566
  if (cfg.concurrencyPolicy !== void 0) envelope.concurrencyPolicy = cfg.concurrencyPolicy;
5040
5567
  if (cfg.workspace !== void 0) envelope.workspace = cfg.workspace;
5568
+ if (cfg.connections !== void 0) envelope.connections = cfg.connections;
5041
5569
  return withDefaultsFilled(envelope);
5042
5570
  }
5043
5571
  };
@@ -5069,16 +5597,16 @@ function stepNodeOf(s) {
5069
5597
  }
5070
5598
  __name(stepNodeOf, "stepNodeOf");
5071
5599
  function materializeEntry(entry, steps) {
5072
- const single = /* @__PURE__ */ __name((n) => {
5073
- if (n.type === "step" && steps[n.step.id]) return stepNodeOf(steps[n.step.id]);
5074
- if (n.type === "workflow" && n.workflowId === WORKFLOW_ARM_SUBRUN_ID && n.graph) return {
5075
- ...n,
5600
+ const single = /* @__PURE__ */ __name((n2) => {
5601
+ if (n2.type === "step" && steps[n2.step.id]) return stepNodeOf(steps[n2.step.id]);
5602
+ if (n2.type === "workflow" && n2.workflowId === WORKFLOW_ARM_SUBRUN_ID && n2.graph) return {
5603
+ ...n2,
5076
5604
  graph: [
5077
- n.graph[0],
5078
- single(n.graph[1])
5605
+ n2.graph[0],
5606
+ single(n2.graph[1])
5079
5607
  ]
5080
5608
  };
5081
- return n;
5609
+ return n2;
5082
5610
  }, "single");
5083
5611
  switch (entry.type) {
5084
5612
  case "step":
@@ -5143,7 +5671,6 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5143
5671
  if (this.steps[s.id] && this.steps[s.id] !== s) throw new LuaWorkflowBuildError("duplicate-step-id", `step id "${s.id}" is declared twice`);
5144
5672
  const tier = s.tier ?? (s.workspace ? "job" : void 0);
5145
5673
  if (s.workspace && s.tier !== void 0 && s.tier !== "job") throw new LuaWorkflowBuildError("workspace-requires-job-tier", `"${s.id}": a step mounting a workspace must be tier:'job'`);
5146
- if (s.workspace && !this.config.workspace) throw new LuaWorkflowBuildError("workspace-not-declared", `"${s.id}" mounts a workspace but createWorkflow declares none`);
5147
5674
  if (s.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${s.id}": jobTools require tier:'job' (job-tools-require-job-tier)`);
5148
5675
  assertTimeout({
5149
5676
  id: s.id,
@@ -5384,13 +5911,20 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5384
5911
  }
5385
5912
  const tier = opts.tier ?? (opts.workspace ? "job" : void 0);
5386
5913
  if (opts.workspace && opts.tier !== void 0 && opts.tier !== "job") throw new LuaWorkflowBuildError("workspace-requires-job-tier", `"${id}": a step mounting a workspace must be tier:'job'`);
5387
- if (opts.workspace && !this.config.workspace) throw new LuaWorkflowBuildError("workspace-not-declared", `"${id}" mounts a workspace but createWorkflow declares none`);
5388
5914
  if (opts.harness !== void 0 && tier !== "job") throw new LuaWorkflowBuildError("harness-requires-job-tier", `"${id}": harness is only legal on a tier:'job' agent step`);
5389
5915
  if (opts.toolScope?.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${id}": toolScope.jobTools require tier:'job' (job-tools-require-job-tier)`);
5390
5916
  if (opts.maxTurns !== void 0) {
5391
5917
  if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxTurns is only legal on a tier:'job' agent step`);
5392
5918
  if (!Number.isInteger(opts.maxTurns) || opts.maxTurns < 1 || opts.maxTurns > 500) throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": maxTurns must be an integer 1..500`);
5393
5919
  }
5920
+ if (opts.maxMessages !== void 0) {
5921
+ if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxMessages is only legal on a tier:'job' agent step`);
5922
+ if (!Number.isInteger(opts.maxMessages) || opts.maxMessages < 1 || opts.maxMessages > 5e3) throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": maxMessages must be an integer 1..5000`);
5923
+ }
5924
+ if (opts.maxInputTokens !== void 0) {
5925
+ if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxInputTokens is only legal on a tier:'job' agent step`);
5926
+ if (!Number.isInteger(opts.maxInputTokens) || opts.maxInputTokens < 1e6 || opts.maxInputTokens > 5e8) throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": maxInputTokens must be an integer 1000000..500000000`);
5927
+ }
5394
5928
  assertTimeout({
5395
5929
  id,
5396
5930
  timeoutSeconds: opts.timeoutSeconds,
@@ -5417,7 +5951,9 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5417
5951
  workspace: opts.workspace,
5418
5952
  jobResources: opts.jobResources,
5419
5953
  harness: opts.harness,
5420
- maxTurns: opts.maxTurns
5954
+ maxTurns: opts.maxTurns,
5955
+ maxMessages: opts.maxMessages,
5956
+ maxInputTokens: opts.maxInputTokens
5421
5957
  });
5422
5958
  return this.push({
5423
5959
  kind: "declare",
@@ -5572,7 +6108,11 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5572
6108
  }
5573
6109
  assertNoClosure(input, `workflow("${id}").input`);
5574
6110
  this.recordEnvRefs(input);
5575
- this.nestedRefs.push({
6111
+ this.nestedRefs.push(opts?.workspace === "inherit" ? {
6112
+ id,
6113
+ name,
6114
+ workspace: "inherit"
6115
+ } : {
5576
6116
  id,
5577
6117
  name
5578
6118
  });
@@ -5584,8 +6124,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5584
6124
  workspace: opts?.workspace
5585
6125
  });
5586
6126
  return this.push({
5587
- kind: "entry",
5588
- entry: node
6127
+ kind: "declare",
6128
+ node
5589
6129
  });
5590
6130
  }
5591
6131
  commit() {
@@ -5595,7 +6135,7 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5595
6135
  const { graph, issues } = resolvePlacements(this.calls);
5596
6136
  const fatal = issues[0];
5597
6137
  if (fatal) {
5598
- const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id }) somewhere in the chain \u2014 before OR after the reference" : void 0;
6138
+ const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026) somewhere in the chain \u2014 before OR after the reference" : void 0;
5599
6139
  throw new LuaWorkflowBuildError(fatal.code, fatal.message, hint);
5600
6140
  }
5601
6141
  if (graph.length === 0) throw new LuaWorkflowBuildError("empty-graph", `workflow "${this.config.name}" has no entries`);
@@ -5608,7 +6148,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5608
6148
  if (graphHasHitl(graph, this.steps) && this.config.budget?.maxDurationSeconds === void 0) {
5609
6149
  this.warnings.push({
5610
6150
  code: "hitl-duration-defaulted",
5611
- message: `budget.maxDurationSeconds defaulted to ${WORKFLOW_HITL_MAX_DURATION_SECONDS} s (30 d) because the graph contains an approval / waitForSignal / suspend-capable step \u2014 set it explicitly to silence`
6151
+ // LUA-668: names the workflow two HITL workflows in one project printed two identical lines that read as a duplicate.
6152
+ message: `workflow "${this.config.name}": budget.maxDurationSeconds defaulted to ${WORKFLOW_HITL_MAX_DURATION_SECONDS} s (30 d) because the graph contains an approval / waitForSignal / suspend-capable step \u2014 set it explicitly to silence`
5612
6153
  });
5613
6154
  }
5614
6155
  this.checkScheduleInput();
@@ -5671,8 +6212,8 @@ function createWorkflow(cfg) {
5671
6212
  if (v.roles.length > 20 || (v.users?.length ?? 0) > 50) throw new LuaWorkflowBuildError("cap-exceeded", "outputVisibility allows \u2264 20 roles and \u2264 50 users");
5672
6213
  }
5673
6214
  if (cfg.backfillOnEnable?.maxOccurrences !== void 0) {
5674
- const n = cfg.backfillOnEnable.maxOccurrences;
5675
- if (!Number.isInteger(n) || n < 1 || n > 200) throw new LuaWorkflowBuildError("invalid-envelope", "backfillOnEnable.maxOccurrences must be an integer in 1..200 (backfill-max-occurrences-out-of-range; the org maxBatchItems twin is checked at publish/R56)");
6215
+ const n2 = cfg.backfillOnEnable.maxOccurrences;
6216
+ if (!Number.isInteger(n2) || n2 < 1 || n2 > 200) throw new LuaWorkflowBuildError("invalid-envelope", "backfillOnEnable.maxOccurrences must be an integer in 1..200 (backfill-max-occurrences-out-of-range; the org maxBatchItems twin is checked at publish/R56)");
5676
6217
  }
5677
6218
  const keys = /* @__PURE__ */ new Set();
5678
6219
  envRefKeys(cfg.schedule, keys);