lua-cli 3.32.1 → 3.32.2

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.
@@ -4,9 +4,6 @@ var __name = (target, value3) => __defProp(target, "name", { value: value3, conf
4
4
  // src/types/workflow.ts
5
5
  import { zodToJsonSchema } from "zod-to-json-schema";
6
6
 
7
- // ../workflow-graph/dist/index.mjs
8
- import { createHash } from "crypto";
9
-
10
7
  // ../shared-types/dist/index.mjs
11
8
  import { z } from "zod";
12
9
  import { z as z2 } from "zod";
@@ -659,6 +656,12 @@ function isImplicitModelSelectionSource(source) {
659
656
  }
660
657
  __name(isImplicitModelSelectionSource, "isImplicitModelSelectionSource");
661
658
  __name2(isImplicitModelSelectionSource, "isImplicitModelSelectionSource");
659
+ var PLATFORM_FALLBACK_MODEL_SOURCE = "platform-fallback";
660
+ function isPlatformFallbackModelSource(source) {
661
+ return source === PLATFORM_FALLBACK_MODEL_SOURCE;
662
+ }
663
+ __name(isPlatformFallbackModelSource, "isPlatformFallbackModelSource");
664
+ __name2(isPlatformFallbackModelSource, "isPlatformFallbackModelSource");
662
665
  function resolveRequireToolApproval(rules) {
663
666
  const raw = rules?.requireToolApproval ?? rules?.requireApproval;
664
667
  if (raw === void 0 || raw === null) return void 0;
@@ -1489,6 +1492,87 @@ function hasCapability(profiles, profileId, required) {
1489
1492
  }
1490
1493
  __name(hasCapability, "hasCapability");
1491
1494
  __name2(hasCapability, "hasCapability");
1495
+ function workflowJobId(id) {
1496
+ switch (id.kind) {
1497
+ case "start":
1498
+ return `wft_${id.runId}_0`;
1499
+ case "start-redrive":
1500
+ return `wft_${id.runId}_start_r${id.now}`;
1501
+ case "tick":
1502
+ return `wft_${id.runId}_${id.seq}`;
1503
+ case "timer":
1504
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}`;
1505
+ case "timer-remainder":
1506
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_r${id.now}`;
1507
+ case "timer-deferral":
1508
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_d${id.now}`;
1509
+ case "timer-sibling":
1510
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_w${id.now}`;
1511
+ case "retry":
1512
+ return `wft_${id.runId}_retry_${id.stepId}_${id.attempt}`;
1513
+ case "respawn":
1514
+ return `wft_${id.runId}_respawn_${id.stepId}_${id.attempt}_s${id.segment}_r${id.now}`;
1515
+ case "redrive":
1516
+ return `wft_${id.runId}_redrive_${id.stepId}_${id.attempt}_r${id.now}`;
1517
+ case "redispatch":
1518
+ return `wft_${id.runId}_redispatch_${id.stepId}_${id.attempt}_x${id.executionId ?? "none"}_r${id.now}`;
1519
+ case "expiry":
1520
+ return `wft_${id.runId}_expire_${id.stepId}_${id.attempt}_t${id.suspendedAt}${id.hop > 0 ? `_h${id.hop}` : ""}`;
1521
+ case "step-terminal":
1522
+ return `wft_${id.runId}_term_${id.stepId}_${id.attempt}_r${id.now}`;
1523
+ case "handback":
1524
+ return `wft_${id.runId}_handback_${id.stepId}_${id.attempt}_${id.now}`;
1525
+ case "foreach-rate":
1526
+ return `wft_${id.runId}_fe_${id.stepId}_${id.attempt}_${id.bucket}`;
1527
+ case "budget-expiry":
1528
+ return `wft_${id.runId}_expiry_budget_r${id.now}`;
1529
+ case "budget-resume":
1530
+ return `wft_${id.runId}_resume_budget_${id.now}`;
1531
+ case "deadline":
1532
+ return `wft_${id.runId}_deadline_${id.deadlineAt}`;
1533
+ case "deadline-remainder":
1534
+ return `wft_${id.runId}_deadline_${id.deadlineAt}_r${id.now}`;
1535
+ case "readmit":
1536
+ return `wft_${id.runId}_readmit_${id.now}`;
1537
+ case "cancel":
1538
+ return `wft_${id.runId}_cancel`;
1539
+ case "reconcile":
1540
+ return `wft_${id.runId}_reconcile_${id.now}`;
1541
+ case "replay":
1542
+ return `wft_${id.runId}_replay_g${id.leaseGeneration}`;
1543
+ case "migration":
1544
+ return `wfm_${id.migrationId}`;
1545
+ default:
1546
+ return assertNever(id);
1547
+ }
1548
+ }
1549
+ __name(workflowJobId, "workflowJobId");
1550
+ __name2(workflowJobId, "workflowJobId");
1551
+ function assertNever(id) {
1552
+ throw new Error(`workflowJobId: unknown wake kind ${JSON.stringify(id)}`);
1553
+ }
1554
+ __name(assertNever, "assertNever");
1555
+ __name2(assertNever, "assertNever");
1556
+ function agentStepJobId(msg) {
1557
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_x${msg.executionId}`;
1558
+ }
1559
+ __name(agentStepJobId, "agentStepJobId");
1560
+ __name2(agentStepJobId, "agentStepJobId");
1561
+ function heavyStepJobId(msg) {
1562
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_x${msg.executionId}`;
1563
+ }
1564
+ __name(heavyStepJobId, "heavyStepJobId");
1565
+ __name2(heavyStepJobId, "heavyStepJobId");
1566
+ function jobSpawnJobId(msg) {
1567
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_s${msg.segment}_${msg.executionId}`;
1568
+ }
1569
+ __name(jobSpawnJobId, "jobSpawnJobId");
1570
+ __name2(jobSpawnJobId, "jobSpawnJobId");
1571
+ function exportAutoJobId(exportId, suffix) {
1572
+ return suffix ? `${exportId}:${suffix}` : exportId;
1573
+ }
1574
+ __name(exportAutoJobId, "exportAutoJobId");
1575
+ __name2(exportAutoJobId, "exportAutoJobId");
1492
1576
  var SYSTEM_USER_PREFIX = "system:";
1493
1577
  function isSystemRun(identity) {
1494
1578
  return identity.userId.startsWith(SYSTEM_USER_PREFIX);
@@ -1620,8 +1704,126 @@ function scheduledWorkflowRunIdForTime(jobId, scheduledTime) {
1620
1704
  __name(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1621
1705
  __name2(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1622
1706
  var WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES = 64 * 1024;
1707
+ var WORKFLOW_RESOLVE_OUTPUT_MAX_BYTES = 256 * 1024;
1708
+ var WORKFLOW_RETRY_BACKOFFS = [
1709
+ "fixed",
1710
+ "exponential"
1711
+ ];
1712
+ var WORKFLOW_JOB_RESOURCES = [
1713
+ "small",
1714
+ "medium",
1715
+ "large"
1716
+ ];
1717
+ var WORKFLOW_SIDE_EFFECTS = [
1718
+ "none",
1719
+ "external"
1720
+ ];
1721
+ var WORKFLOW_JOB_RANGES = Object.freeze({
1722
+ /** Coding-turn cap (`claude -p --max-turns`); absent ⇒ the cluster default (LUA_WF_JOB_MAX_TURNS). */
1723
+ maxTurns: Object.freeze({
1724
+ min: 1,
1725
+ max: 500
1726
+ }),
1727
+ /** Harness messages per attempt (one per content block); absent ⇒ the cluster default (400). */
1728
+ maxMessages: Object.freeze({
1729
+ min: 1,
1730
+ max: 5e3
1731
+ }),
1732
+ /** Input-side tokens per attempt (prompt + cache); absent ⇒ the cluster default (4M — LUA-708, was 30M). */
1733
+ maxInputTokens: Object.freeze({
1734
+ min: 1e6,
1735
+ max: 5e8
1736
+ })
1737
+ });
1738
+ var WORKFLOW_JOB_RANGE_MEMBERS = Object.keys(WORKFLOW_JOB_RANGES);
1739
+ function isWithinWorkflowJobRange(member, value3) {
1740
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
1741
+ return typeof value3 === "number" && Number.isInteger(value3) && value3 >= min && value3 <= max;
1742
+ }
1743
+ __name(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1744
+ __name2(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1745
+ function workflowJobRangeMessage(member) {
1746
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
1747
+ return `\`${member}\` must be an integer ${min}..${max}`;
1748
+ }
1749
+ __name(workflowJobRangeMessage, "workflowJobRangeMessage");
1750
+ __name2(workflowJobRangeMessage, "workflowJobRangeMessage");
1751
+ var WORKFLOW_SINGLE_STEP_TYPES = [
1752
+ "agent",
1753
+ "tool",
1754
+ "workflow",
1755
+ "step"
1756
+ ];
1757
+ var WORKFLOW_HITL_ENTRY_TYPES = [
1758
+ "approval",
1759
+ "waitForSignal"
1760
+ ];
1761
+ var WORKFLOW_ARM_ENTRY_TYPES = [
1762
+ ...WORKFLOW_SINGLE_STEP_TYPES,
1763
+ ...WORKFLOW_HITL_ENTRY_TYPES
1764
+ ];
1765
+ var WORKFLOW_HITL_ARM_CONTAINERS = [
1766
+ "parallel",
1767
+ "conditional",
1768
+ "foreach"
1769
+ ];
1770
+ function isWorkflowSingleStepType(type) {
1771
+ return typeof type === "string" && WORKFLOW_SINGLE_STEP_TYPES.includes(type);
1772
+ }
1773
+ __name(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1774
+ __name2(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1775
+ function isWorkflowHitlEntryType(type) {
1776
+ return typeof type === "string" && WORKFLOW_HITL_ENTRY_TYPES.includes(type);
1777
+ }
1778
+ __name(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1779
+ __name2(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1780
+ function isWorkflowArmEntryType(type) {
1781
+ return typeof type === "string" && WORKFLOW_ARM_ENTRY_TYPES.includes(type);
1782
+ }
1783
+ __name(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1784
+ __name2(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1785
+ function workflowContainerRunsHitlArm(container) {
1786
+ return typeof container === "string" && WORKFLOW_HITL_ARM_CONTAINERS.includes(container);
1787
+ }
1788
+ __name(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1789
+ __name2(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1790
+ function workflowHitlArmUnsupportedMessage(type, id, container) {
1791
+ const legal = WORKFLOW_HITL_ARM_CONTAINERS.map((c) => `\`${c}\``).join(" / ");
1792
+ return `the engine does not run \`${type}\` inside a \`${container}\` yet (node "${id}") \u2014 place it at the top level, as a ${legal} arm, or inside a child \`workflow\``;
1793
+ }
1794
+ __name(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1795
+ __name2(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1796
+ function workflowHitlArmShapeMessage(type, id, shape) {
1797
+ return shape === "mapped-arm" ? `\`${type}\` arm "${id}" takes the previous output as its payload \u2014 it cannot head a [mapping, step] chain; map before the container instead` : `a chunked foreach hands each child a slice of items, not one \u2014 \`${type}\` body "${id}" takes one item; drop \`chunk\``;
1798
+ }
1799
+ __name(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1800
+ __name2(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1801
+ var WORKFLOW_GRAPH_ENTRY_STEP_KINDS = Object.freeze({
1802
+ agent: "agent",
1803
+ tool: "tool",
1804
+ workflow: "subrun",
1805
+ step: "code",
1806
+ mapping: "map",
1807
+ sleep: "sleep",
1808
+ sleepUntil: "sleepUntil",
1809
+ parallel: null,
1810
+ conditional: "branch",
1811
+ foreach: "foreach",
1812
+ loop: "loop",
1813
+ approval: "approval",
1814
+ waitForSignal: "signal"
1815
+ });
1816
+ var WORKFLOW_ARM_ENTRY_STEP_KINDS = Object.freeze(Object.fromEntries(WORKFLOW_ARM_ENTRY_TYPES.map((t) => [
1817
+ t,
1818
+ WORKFLOW_GRAPH_ENTRY_STEP_KINDS[t]
1819
+ ])));
1820
+ var WORKFLOW_BUDGET_MAX_DURATION_SECONDS = Object.freeze({
1821
+ min: 60,
1822
+ max: 2592e3
1823
+ });
1623
1824
  var REDACTED_PLACEHOLDER = "[REDACTED]";
1624
1825
  var PROVIDER_MESSAGE_MAX_CHARS = 300;
1826
+ var ERROR_MESSAGE_MAX_CHARS = 2e3;
1625
1827
  var SECRET_LITERAL_PATTERNS = [
1626
1828
  {
1627
1829
  re: /\b(github_pat_)[A-Za-z0-9_]{16,}/g
@@ -1632,6 +1834,10 @@ var SECRET_LITERAL_PATTERNS = [
1632
1834
  {
1633
1835
  re: /\b(glpat-)[A-Za-z0-9_-]{16,}/g
1634
1836
  },
1837
+ // LUA-696 review (3): the npm granular / classic token (`npm_` + 36 alphanumerics).
1838
+ {
1839
+ re: /\b(npm_)[A-Za-z0-9]{36}\b/g
1840
+ },
1635
1841
  {
1636
1842
  re: /\b(sk-ant-)[A-Za-z0-9_-]{16,}/g
1637
1843
  },
@@ -1668,8 +1874,15 @@ var SECRET_PAIR_PATTERNS = [
1668
1874
  // `FOO_TOKEN=x`, `secretKey=x`, `password=x`. Groups: the char before the name, the name, the separator
1669
1875
  // (with its quotes), the scheme word — all kept; the value goes. A value a literal rule already replaced
1670
1876
  // (`x-access-token:[REDACTED]@host`) is left alone so the host after it survives.
1877
+ // LUA-696: the second lookahead keeps a scrubbed `Authorization: Bearer [REDACTED]` as it is — without it the
1878
+ // optional scheme group backtracks to empty and `Bearer` itself becomes the value (`[REDACTED] [REDACTED]`).
1879
+ // The scrub is applied more than once on purpose (the Job site at its copy, the outcome table at the row
1880
+ // write, lua-api on the way out), so it must be idempotent.
1881
+ // LUA-696 review (HIGH): the identifier prefix is BOUNDED (`{1,64}`) — unbounded, `[A-Za-z0-9-]+` made the
1882
+ // scan quadratic on `-`-heavy text (100 KB of `-` took 12.7 s on the lua-core event loop; a worker-tier step
1883
+ // can throw that). No identifier that names a credential is longer; the probe list is unchanged.
1671
1884
  {
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")
1885
+ re: new RegExp(`(^|[^A-Za-z0-9])((?:[A-Za-z0-9-]{1,64}[_-])?${SECRET_NAME}|[A-Za-z0-9-]{1,64}_key)(["']?\\s*[:=]\\s*["']?)((?:basic\\s+|bearer\\s+|token\\s+)?)(?!\\[REDACTED\\]|(?:basic|bearer|token)\\s+\\[REDACTED\\])[^\\s"',;)}&]{4,}`, "gi")
1673
1886
  },
1674
1887
  // `?token=x`, `&key=x`, `&X-Amz-Signature=x`, `&sig=x`
1675
1888
  {
@@ -1696,6 +1909,34 @@ function groupCount(re) {
1696
1909
  }
1697
1910
  __name(groupCount, "groupCount");
1698
1911
  __name2(groupCount, "groupCount");
1912
+ var WORKFLOW_SECRET_KEY_RE = /(^|[_\-.\s])(secret|token|password|passwd|pwd|passphrase|api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|authorization|auth[_-]?token|access[_-]?token|id[_-]?token|refresh[_-]?token|session[_-]?key|credential(s)?)([_\-.\s]|$)|^(secret|token|password|passwd|pwd|passphrase|apikey|authorization|credentials?)$/i;
1913
+ var WORKFLOW_RESERVED_SECRET_KEYS = Object.freeze([
1914
+ "secret",
1915
+ "token",
1916
+ "password",
1917
+ "passwd",
1918
+ "pwd",
1919
+ "passphrase",
1920
+ "api_key",
1921
+ "apikey",
1922
+ "access_key",
1923
+ "secret_key",
1924
+ "private_key",
1925
+ "client_secret",
1926
+ "authorization",
1927
+ "auth_token",
1928
+ "access_token",
1929
+ "id_token",
1930
+ "refresh_token",
1931
+ "session_key",
1932
+ "credential",
1933
+ "credentials"
1934
+ ]);
1935
+ function isWorkflowSecretKey(key) {
1936
+ return WORKFLOW_SECRET_KEY_RE.test(key);
1937
+ }
1938
+ __name(isWorkflowSecretKey, "isWorkflowSecretKey");
1939
+ __name2(isWorkflowSecretKey, "isWorkflowSecretKey");
1699
1940
  function applyPatterns(text, patterns) {
1700
1941
  let out = text;
1701
1942
  for (const { re, suffix } of patterns) {
@@ -1722,8 +1963,19 @@ function scrubSecretText(text) {
1722
1963
  }
1723
1964
  __name(scrubSecretText, "scrubSecretText");
1724
1965
  __name2(scrubSecretText, "scrubSecretText");
1966
+ var SCRUB_INPUT_MAX_CHARS = 16 * 1024;
1967
+ var SCRUB_CUT_BACKOFF_CHARS = 256;
1968
+ function boundScrubInput(text, max = SCRUB_INPUT_MAX_CHARS) {
1969
+ if (typeof text !== "string" || text.length <= max) return text;
1970
+ const window = text.slice(Math.max(0, max - SCRUB_CUT_BACKOFF_CHARS), max);
1971
+ const ws = window.search(/\s\S*$/);
1972
+ const cut = ws >= 0 ? max - window.length + ws : max;
1973
+ return `${text.slice(0, cut)}\u2026`;
1974
+ }
1975
+ __name(boundScrubInput, "boundScrubInput");
1976
+ __name2(boundScrubInput, "boundScrubInput");
1725
1977
  function scrubSecretLines(lines) {
1726
- return lines.map((l) => typeof l === "string" ? scrubSecretText(l) : l);
1978
+ return lines.map((l) => typeof l === "string" ? scrubSecretText(boundScrubInput(l)) : l);
1727
1979
  }
1728
1980
  __name(scrubSecretLines, "scrubSecretLines");
1729
1981
  __name2(scrubSecretLines, "scrubSecretLines");
@@ -1744,13 +1996,18 @@ function messageText(value3) {
1744
1996
  __name(messageText, "messageText");
1745
1997
  __name2(messageText, "messageText");
1746
1998
  function scrubProviderMessage(raw, max = PROVIDER_MESSAGE_MAX_CHARS) {
1747
- const text = messageText(raw).replace(/\s+/g, " ").trim();
1999
+ const text = boundScrubInput(messageText(raw)).replace(/\s+/g, " ").trim();
1748
2000
  if (!text) return void 0;
1749
2001
  const out = scrubSecretText(text);
1750
2002
  return out.length > max ? `${out.slice(0, max - 1)}\u2026` : out;
1751
2003
  }
1752
2004
  __name(scrubProviderMessage, "scrubProviderMessage");
1753
2005
  __name2(scrubProviderMessage, "scrubProviderMessage");
2006
+ function scrubStepErrorMessage(raw) {
2007
+ return scrubProviderMessage(raw, ERROR_MESSAGE_MAX_CHARS);
2008
+ }
2009
+ __name(scrubStepErrorMessage, "scrubStepErrorMessage");
2010
+ __name2(scrubStepErrorMessage, "scrubStepErrorMessage");
1754
2011
  var WORKFLOW_AUDIT_EVENTS = [
1755
2012
  // --- definitions / versions / templates (13, 11 §11.11.5) ---
1756
2013
  "workflow.published",
@@ -1962,23 +2219,33 @@ __name(renderTargetsBlock, "renderTargetsBlock");
1962
2219
  __name2(renderTargetsBlock, "renderTargetsBlock");
1963
2220
 
1964
2221
  // ../workflow-graph/dist/index.mjs
2222
+ import { createHash } from "crypto";
1965
2223
  import { z as z4 } from "zod";
1966
2224
  import { z as z22 } from "zod";
1967
2225
  import { createHash as createHash2 } from "crypto";
1968
2226
  var __defProp3 = Object.defineProperty;
1969
2227
  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
2228
  var SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
1980
2229
  var JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
1981
2230
  var WORKFLOW_ARM_SUBRUN_ID = "$arm";
2231
+ var WORKSPACE_TEMPLATE_EXPR_RE = /^\$\{\s*(?:initData|input)\.([^}]+?)\s*\}$/;
2232
+ function workspaceTemplatePath(template22) {
2233
+ const key = template22.trim();
2234
+ const expr = WORKSPACE_TEMPLATE_EXPR_RE.exec(key);
2235
+ if (expr) return expr[1].split(".");
2236
+ if (key.includes("${")) return void 0;
2237
+ return key.replace(/^(?:input|initData)\./, "").split(".");
2238
+ }
2239
+ __name(workspaceTemplatePath, "workspaceTemplatePath");
2240
+ __name3(workspaceTemplatePath, "workspaceTemplatePath");
2241
+ function retryBackoffs() {
2242
+ if (!Array.isArray(WORKFLOW_RETRY_BACKOFFS)) {
2243
+ throw new Error("@lua/shared-types.WORKFLOW_RETRY_BACKOFFS is not a tuple \u2014 a jest.mock('@lua/shared-types') must spread jest.requireActual('@lua/shared-types')");
2244
+ }
2245
+ return WORKFLOW_RETRY_BACKOFFS;
2246
+ }
2247
+ __name(retryBackoffs, "retryBackoffs");
2248
+ __name3(retryBackoffs, "retryBackoffs");
1982
2249
  var SLEEP_UNTIL_REPLACEMENT = Object.freeze({
1983
2250
  type: "sleep",
1984
2251
  duration: 6e4
@@ -2046,8 +2313,30 @@ function fillSingle(node) {
2046
2313
  }
2047
2314
  __name(fillSingle, "fillSingle");
2048
2315
  __name3(fillSingle, "fillSingle");
2316
+ function fillHitl(node) {
2317
+ if (node.type === "approval") {
2318
+ const a = node;
2319
+ if (a.approver === void 0) a.approver = "creator";
2320
+ if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2321
+ if (a.onTimeout === void 0) a.onTimeout = "deny";
2322
+ if (a.onDeny === void 0) a.onDeny = "continue";
2323
+ if (a.excludeInitiator === void 0) a.excludeInitiator = false;
2324
+ if (a.editable === void 0) a.editable = false;
2325
+ return;
2326
+ }
2327
+ const w = node;
2328
+ if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2329
+ if (w.onTimeout === void 0) w.onTimeout = "fail";
2330
+ if (w.acceptedSources === void 0) w.acceptedSources = [
2331
+ ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
2332
+ ];
2333
+ }
2334
+ __name(fillHitl, "fillHitl");
2335
+ __name3(fillHitl, "fillHitl");
2049
2336
  function fillArm(arm) {
2050
- if (arm.type !== "mapping") fillSingle(arm);
2337
+ if (arm.type === "mapping") return;
2338
+ if (isHitlNode(arm)) fillHitl(arm);
2339
+ else fillSingle(arm);
2051
2340
  }
2052
2341
  __name(fillArm, "fillArm");
2053
2342
  __name3(fillArm, "fillArm");
@@ -2060,7 +2349,7 @@ function fillEntry(entry) {
2060
2349
  fillSingle(entry);
2061
2350
  return;
2062
2351
  case "parallel":
2063
- entry.steps.forEach(fillSingle);
2352
+ entry.steps.forEach(fillArm);
2064
2353
  return;
2065
2354
  case "conditional": {
2066
2355
  const c = entry;
@@ -2074,34 +2363,19 @@ function fillEntry(entry) {
2074
2363
  f.opts = f.opts ?? {};
2075
2364
  if (f.opts.concurrency === void 0) f.opts.concurrency = WORKFLOW_FOREACH_DEFAULT_CONCURRENCY;
2076
2365
  if (f.opts.maxItems === void 0) f.opts.maxItems = WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2077
- fillSingle(f.step);
2366
+ fillArm(f.step);
2078
2367
  return;
2079
2368
  }
2080
2369
  case "loop": {
2081
2370
  const l = entry;
2082
2371
  if (l.maxIterations === void 0) l.maxIterations = WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
2083
- fillSingle(l.step);
2084
- return;
2085
- }
2086
- case "approval": {
2087
- const a = entry;
2088
- if (a.approver === void 0) a.approver = "creator";
2089
- if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2090
- if (a.onTimeout === void 0) a.onTimeout = "deny";
2091
- if (a.onDeny === void 0) a.onDeny = "continue";
2092
- if (a.excludeInitiator === void 0) a.excludeInitiator = false;
2093
- if (a.editable === void 0) a.editable = false;
2372
+ fillArm(l.step);
2094
2373
  return;
2095
2374
  }
2096
- case "waitForSignal": {
2097
- const w = entry;
2098
- if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2099
- if (w.onTimeout === void 0) w.onTimeout = "fail";
2100
- if (w.acceptedSources === void 0) w.acceptedSources = [
2101
- ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
2102
- ];
2375
+ case "approval":
2376
+ case "waitForSignal":
2377
+ fillHitl(entry);
2103
2378
  return;
2104
- }
2105
2379
  case "mapping":
2106
2380
  case "sleep":
2107
2381
  case "sleepUntil":
@@ -2179,6 +2453,8 @@ var schemaIsArray = /* @__PURE__ */ __name3((schema) => {
2179
2453
  if (t === void 0) return void 0;
2180
2454
  return Array.isArray(t) ? t.includes("array") : t === "array";
2181
2455
  }, "schemaIsArray");
2456
+ var isHitlNode = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
2457
+ var isSingleStep = /* @__PURE__ */ __name3((n2) => !isHitlNode(n2), "isSingleStep");
2182
2458
  var singleId = /* @__PURE__ */ __name3((s) => s.type === "step" ? s.step.id : s.id, "singleId");
2183
2459
  var armId = /* @__PURE__ */ __name3((a) => a.type === "mapping" ? a.id : singleId(a), "armId");
2184
2460
  var TEMPLATE_STEP_REF = /\$\{\s*stepResults\.([A-Za-z0-9_\-]+)/g;
@@ -2189,14 +2465,21 @@ function templateStepRefs(text) {
2189
2465
  }
2190
2466
  __name(templateStepRefs, "templateStepRefs");
2191
2467
  __name3(templateStepRefs, "templateStepRefs");
2192
- function mapConfigStepRefs(raw) {
2193
- if (!raw) return [];
2194
- let cfg;
2468
+ function readMapConfig(raw) {
2469
+ if (!raw) return void 0;
2470
+ if (typeof raw !== "string") return raw;
2195
2471
  try {
2196
- cfg = typeof raw === "string" ? JSON.parse(raw) : raw;
2472
+ const cfg = JSON.parse(raw);
2473
+ return cfg && typeof cfg === "object" && !Array.isArray(cfg) ? cfg : void 0;
2197
2474
  } catch {
2198
- return [];
2475
+ return void 0;
2199
2476
  }
2477
+ }
2478
+ __name(readMapConfig, "readMapConfig");
2479
+ __name3(readMapConfig, "readMapConfig");
2480
+ function mapConfigStepRefs(raw) {
2481
+ const cfg = readMapConfig(raw);
2482
+ if (!cfg) return [];
2200
2483
  const ids = [];
2201
2484
  for (const d of Object.values(cfg)) {
2202
2485
  if (!d || typeof d !== "object") continue;
@@ -2271,6 +2554,16 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2271
2554
  if (envelopeWorkspace?.backend && envelopeWorkspace.backend !== "ebs" && opts.policy?.workspaceBackends && !opts.policy.workspaceBackends.includes(envelopeWorkspace.backend)) {
2272
2555
  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");
2273
2556
  }
2557
+ for (const member of [
2558
+ "repo",
2559
+ "ref"
2560
+ ]) {
2561
+ const v = envelopeWorkspace?.[member];
2562
+ const tpl = v && typeof v === "object" && typeof v.template === "string" ? v.template : void 0;
2563
+ if (tpl !== void 0 && workspaceTemplatePath(tpl) === void 0) {
2564
+ warn("workspace-template-composite", `workspace.${member} template ${JSON.stringify(tpl)} is a composite \u2014 the engine binds one whole-string \${initData.<path>} (or an input.<path> key) and every start would fail workspace_provision_failed{binding_unresolved}; put the assembled value in the run input and bind that path`, `workspace.${member}`);
2565
+ }
2566
+ }
2274
2567
  const declaredKeys = new Set(opts.connectionKeys ?? []);
2275
2568
  if (g.connections !== void 0 && !Array.isArray(g.connections)) {
2276
2569
  err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
@@ -2323,8 +2616,10 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2323
2616
  const r = node.retry;
2324
2617
  if (!r) return;
2325
2618
  const id = singleId(node);
2326
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") {
2327
- err("backoff-invalid", `retry.backoff must be 'fixed' | 'exponential'`, `${path}.retry.backoff`, id);
2619
+ const backoffs = retryBackoffs();
2620
+ if (r.backoff !== void 0 && !backoffs.includes(r.backoff)) {
2621
+ const list = backoffs.map((b) => `'${b}'`).join(" | ");
2622
+ err("backoff-invalid", `retry.backoff must be ${list}`, `${path}.retry.backoff`, id);
2328
2623
  }
2329
2624
  if (r.backoffSeconds !== void 0 && r.backoffSeconds < 0) {
2330
2625
  err("backoff-invalid", "retry.backoffSeconds must be \u2265 0", `${path}.retry.backoffSeconds`, id);
@@ -2476,8 +2771,13 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2476
2771
  err("container-arm-empty", "a bare mapping arm has nothing to run", `${path}.graph.1`, node.id);
2477
2772
  return;
2478
2773
  }
2774
+ const inner = body[1];
2775
+ if (isHitlNode(inner)) {
2776
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(inner.type, inner.id, "mapped-arm"), `${path}.graph.1`, inner.id);
2777
+ return;
2778
+ }
2479
2779
  upstream.add(singleId(body[1]));
2480
- checkArm(body[0], `${path}.graph.0`, depth);
2780
+ checkArm(body[0], `${path}.graph.0`, depth, "parallel");
2481
2781
  checkSingle(body[1], `${path}.graph.1`, depth);
2482
2782
  upstream.add(body[0].id);
2483
2783
  upstream.add(singleId(body[1]));
@@ -2509,13 +2809,60 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2509
2809
  if (node.type === "workflow" && node.kind === "subrun" && depth > caps.maxNestingDepth) {
2510
2810
  err("cap-exceeded", `nesting depth ${depth} exceeds ${caps.maxNestingDepth}`, path, node.id);
2511
2811
  }
2812
+ if (node.type === "workflow" && node.workflowId !== WORKFLOW_ARM_SUBRUN_ID && typeof g.definition?.id === "string" && node.workflowId === g.definition.id) {
2813
+ err("subrun-cycle", `"${node.id}" starts "${node.workflowId}", which is this workflow itself`, path, node.id);
2814
+ }
2512
2815
  for (const ref of nodeStepRefs(node)) {
2513
2816
  if (!upstream.has(ref)) {
2514
2817
  err("template-reference-unresolved", `"${singleId(node)}" references stepResults.${ref}, which is not upstream`, path, singleId(node));
2515
2818
  }
2516
2819
  }
2517
2820
  }, "checkSingle");
2518
- const checkArm = /* @__PURE__ */ __name3((arm, path, depth) => {
2821
+ const checkHitl = /* @__PURE__ */ __name3((node, path) => {
2822
+ if (node.type === "waitForSignal") {
2823
+ const w = node;
2824
+ checkId(w.id, path);
2825
+ if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path}.signal`, w.id);
2826
+ return;
2827
+ }
2828
+ const a = node;
2829
+ checkId(a.id, path);
2830
+ if (a.approver === "creator" && a.excludeInitiator === true) {
2831
+ err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path, a.id);
2832
+ }
2833
+ if (a.fourEyes !== void 0 && a.editable !== true) {
2834
+ err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path}.fourEyes`, a.id);
2835
+ }
2836
+ if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2837
+ err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path}.editablePaths`, a.id);
2838
+ }
2839
+ for (const p of a.editablePaths ?? []) {
2840
+ if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path}.editablePaths`, a.id);
2841
+ }
2842
+ if (Array.isArray(a.onTimeout)) {
2843
+ const chain = a.onTimeout;
2844
+ const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2845
+ if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path}.onTimeout`, a.id);
2846
+ const last = chain[chain.length - 1];
2847
+ if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2848
+ err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path}.onTimeout`, a.id);
2849
+ }
2850
+ }
2851
+ if (typeof a.details === "string") {
2852
+ for (const ref of templateStepRefs(a.details)) {
2853
+ if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path, a.id);
2854
+ }
2855
+ }
2856
+ }, "checkHitl");
2857
+ const checkHitlArm = /* @__PURE__ */ __name3((node, path, container) => {
2858
+ if (!workflowContainerRunsHitlArm(container)) {
2859
+ checkId(node.id, path);
2860
+ err("node-type-unsupported-in-container", workflowHitlArmUnsupportedMessage(node.type, node.id, container), path, node.id);
2861
+ return;
2862
+ }
2863
+ checkHitl(node, path);
2864
+ }, "checkHitlArm");
2865
+ const checkArm = /* @__PURE__ */ __name3((arm, path, depth, container) => {
2519
2866
  if (arm.type === "mapping") {
2520
2867
  checkId(arm.id, path);
2521
2868
  for (const ref of nodeStepRefs(arm)) {
@@ -2523,6 +2870,10 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2523
2870
  }
2524
2871
  return;
2525
2872
  }
2873
+ if (isHitlNode(arm)) {
2874
+ checkHitlArm(arm, path, container);
2875
+ return;
2876
+ }
2526
2877
  checkSingle(arm, path, depth);
2527
2878
  }, "checkArm");
2528
2879
  graph.forEach((entry, i) => {
@@ -2572,18 +2923,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2572
2923
  err("mapping-placement", "a bare mapping cannot be a parallel arm \u2014 chain it as [map, step]", armPath);
2573
2924
  return;
2574
2925
  }
2575
- if (arm.type === "approval" || arm.type === "waitForSignal") {
2576
- err("approval-inside-container", "approval / waitForSignal are top-level only in v1", armPath);
2577
- return;
2578
- }
2579
- checkSingle(arm, armPath, 1);
2926
+ checkArm(arm, armPath, 1, "parallel");
2580
2927
  declared.push(singleId(arm));
2581
2928
  });
2582
- const worktreeArms = p.steps.filter((a) => a.workspace?.isolation === "worktree");
2929
+ const executableArms = p.steps.filter(isSingleStep);
2930
+ const worktreeArms = executableArms.filter((a) => a.workspace?.isolation === "worktree");
2583
2931
  if (worktreeArms.length > 0 && !p.merge) err("worktree-arms-require-merge", "worktree arms need a `merge` policy", path);
2584
2932
  if (worktreeArms.length === 0 && p.merge) err("merge-requires-worktree-arms", "`merge` needs at least one worktree arm", path);
2585
2933
  if (worktreeArms.length > WORKFLOW_JOB_MAX_WORKTREE_ARMS) err("worktree-arms-exceed-cap", `at most ${WORKFLOW_JOB_MAX_WORKTREE_ARMS} worktree arms per parallel`, path);
2586
- const sharedArms = p.steps.filter((a) => {
2934
+ const sharedArms = executableArms.filter((a) => {
2587
2935
  const w = workspaceOf(a);
2588
2936
  if (!w || w === "inherit" || w.isolation === "worktree") return false;
2589
2937
  return !(envelopeWorkspace?.backend === "efs" && w.mount === "ro");
@@ -2600,11 +2948,11 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2600
2948
  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}`);
2601
2949
  });
2602
2950
  c.steps.forEach((arm, j) => {
2603
- checkArm(arm, `${path}.steps.${j}`, 1);
2951
+ checkArm(arm, `${path}.steps.${j}`, 1, "conditional");
2604
2952
  declared.push(armId(arm));
2605
2953
  });
2606
2954
  if (c.otherwise) {
2607
- checkArm(c.otherwise, `${path}.otherwise`, 1);
2955
+ checkArm(c.otherwise, `${path}.otherwise`, 1, "conditional");
2608
2956
  declared.push(armId(c.otherwise));
2609
2957
  }
2610
2958
  break;
@@ -2627,8 +2975,9 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2627
2975
  }
2628
2976
  if (o.chunk !== void 0) {
2629
2977
  const max = o.maxItems ?? WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2630
- if (!Number.isInteger(o.chunk.size) || o.chunk.size < 1 || o.chunk.size > max) {
2631
- err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, `${path}.opts.chunk.size`);
2978
+ const size = typeof o.chunk === "number" ? o.chunk : o.chunk.size;
2979
+ if (!Number.isInteger(size) || size < 1 || size > max) {
2980
+ err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, typeof o.chunk === "number" ? `${path}.opts.chunk` : `${path}.opts.chunk.size`);
2632
2981
  }
2633
2982
  }
2634
2983
  if (o.rateLimit !== void 0) {
@@ -2645,12 +2994,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2645
2994
  const bodyType = f.step.type;
2646
2995
  const prevEntry = i > 0 ? graph[i - 1] : void 0;
2647
2996
  if (bodyType !== "mapping" && prevEntry?.type === "mapping" && prevEntry.id === `${singleId(f.step)}_items`) {
2648
- let cfg;
2649
- try {
2650
- cfg = JSON.parse(prevEntry.mapConfig);
2651
- } catch {
2652
- cfg = void 0;
2653
- }
2997
+ const cfg = readMapConfig(prevEntry.mapConfig);
2654
2998
  const d = cfg?.[""];
2655
2999
  let arrayLike;
2656
3000
  if (d && "value" in d) arrayLike = Array.isArray(d.value);
@@ -2662,8 +3006,13 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2662
3006
  if (arrayLike === false) err("foreach-items-not-array", `foreach({items}) for "${singleId(f.step)}" binds a value that is not an array`, `${path}.step`, singleId(f.step));
2663
3007
  }
2664
3008
  if (bodyType === "mapping") err("container-arm-empty", "a foreach body needs a step, not a bare mapping", `${path}.step`);
2665
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
2666
- else {
3009
+ else if (isHitlNode(f.step)) {
3010
+ if (o.chunk !== void 0) {
3011
+ checkId(f.step.id, `${path}.step`);
3012
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(f.step.type, f.step.id, "chunked-foreach"), `${path}.step`, f.step.id);
3013
+ } else checkHitlArm(f.step, `${path}.step`, "foreach");
3014
+ declared.push(f.step.id);
3015
+ } else {
2667
3016
  checkSingle(f.step, `${path}.step`, o.chunk ? 2 : 1);
2668
3017
  declared.push(singleId(f.step));
2669
3018
  }
@@ -2680,52 +3029,20 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2680
3029
  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`);
2681
3030
  const bodyType = l.step.type;
2682
3031
  if (bodyType === "mapping") err("container-arm-empty", "a loop body needs a step, not a bare mapping", `${path}.step`);
2683
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
2684
- else {
3032
+ else if (isHitlNode(l.step)) {
3033
+ checkHitlArm(l.step, `${path}.step`, "loop");
3034
+ declared.push(l.step.id);
3035
+ } else {
2685
3036
  checkSingle(l.step, `${path}.step`, 1);
2686
3037
  declared.push(singleId(l.step));
2687
3038
  }
2688
3039
  break;
2689
3040
  }
2690
- case "approval": {
2691
- const a = entry;
2692
- checkId(a.id, path);
2693
- if (a.approver === "creator" && a.excludeInitiator === true) {
2694
- err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path, a.id);
2695
- }
2696
- if (a.fourEyes !== void 0 && a.editable !== true) {
2697
- err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path}.fourEyes`, a.id);
2698
- }
2699
- if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2700
- err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path}.editablePaths`, a.id);
2701
- }
2702
- for (const p of a.editablePaths ?? []) {
2703
- if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path}.editablePaths`, a.id);
2704
- }
2705
- if (Array.isArray(a.onTimeout)) {
2706
- const chain = a.onTimeout;
2707
- const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2708
- if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path}.onTimeout`, a.id);
2709
- const last = chain[chain.length - 1];
2710
- if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2711
- err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path}.onTimeout`, a.id);
2712
- }
2713
- }
2714
- if (typeof a.details === "string") {
2715
- for (const ref of templateStepRefs(a.details)) {
2716
- if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path, a.id);
2717
- }
2718
- }
2719
- declared.push(a.id);
2720
- break;
2721
- }
2722
- case "waitForSignal": {
2723
- const w = entry;
2724
- checkId(w.id, path);
2725
- if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path}.signal`, w.id);
2726
- declared.push(w.id);
3041
+ case "approval":
3042
+ case "waitForSignal":
3043
+ checkHitl(entry, path);
3044
+ declared.push(entry.id);
2727
3045
  break;
2728
- }
2729
3046
  default:
2730
3047
  err("invalid-envelope", `unknown entry type "${entry.type}"`, path);
2731
3048
  }
@@ -2846,14 +3163,9 @@ var WorkflowPlanError = class extends Error {
2846
3163
  this.name = "WorkflowPlanError";
2847
3164
  }
2848
3165
  };
2849
- var SINGLE_STEP_KINDS = {
2850
- step: "code",
2851
- agent: "agent",
2852
- tool: "tool",
2853
- workflow: "subrun"
2854
- };
2855
- var isSingleStep = /* @__PURE__ */ __name3((e) => e.type === "step" || e.type === "agent" || e.type === "tool" || e.type === "workflow", "isSingleStep");
2856
- var singleStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "singleStepId");
3166
+ var isArmStep = /* @__PURE__ */ __name3((e) => isWorkflowArmEntryType(e.type), "isArmStep");
3167
+ var armStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "armStepId");
3168
+ var armStepKind = /* @__PURE__ */ __name3((e) => WORKFLOW_ARM_ENTRY_STEP_KINDS[e.type], "armStepKind");
2857
3169
  var joinIdOf = /* @__PURE__ */ __name3((entryId) => `${entryId}.join`, "joinIdOf");
2858
3170
  var containerIdOf = /* @__PURE__ */ __name3((type, entryIndex) => `${type}@${entryIndex}`, "containerIdOf");
2859
3171
  function compilePlan(g) {
@@ -2869,10 +3181,10 @@ function compilePlan(g) {
2869
3181
  let prevTails = [];
2870
3182
  const foreachJoinToEntry = /* @__PURE__ */ new Map();
2871
3183
  g.definition.graph.forEach((entry, entryIndex) => {
2872
- if (isSingleStep(entry)) {
2873
- const id = singleStepId(entry);
3184
+ if (isArmStep(entry)) {
3185
+ const id = armStepId(entry);
2874
3186
  addNode(id, {
2875
- kind: SINGLE_STEP_KINDS[entry.type],
3187
+ kind: armStepKind(entry),
2876
3188
  dependsOn: prevTails,
2877
3189
  downstream: [],
2878
3190
  unsatisfiedDeps: prevTails.length,
@@ -2897,9 +3209,8 @@ function compilePlan(g) {
2897
3209
  ];
2898
3210
  return;
2899
3211
  case "sleep":
2900
- case "sleepUntil":
2901
- case "approval": {
2902
- const kind = entry.type === "approval" ? "approval" : entry.type;
3212
+ case "sleepUntil": {
3213
+ const kind = entry.type;
2903
3214
  addNode(entry.id, {
2904
3215
  kind,
2905
3216
  dependsOn: prevTails,
@@ -2912,24 +3223,12 @@ function compilePlan(g) {
2912
3223
  ];
2913
3224
  return;
2914
3225
  }
2915
- case "waitForSignal":
2916
- addNode(entry.id, {
2917
- kind: "signal",
2918
- dependsOn: prevTails,
2919
- downstream: [],
2920
- unsatisfiedDeps: prevTails.length,
2921
- entry
2922
- });
2923
- prevTails = [
2924
- entry.id
2925
- ];
2926
- return;
2927
3226
  case "parallel": {
2928
3227
  const entryId = containerIdOf("parallel", entryIndex);
2929
3228
  const childIds = entry.steps.map((arm) => {
2930
- const childId = singleStepId(arm);
3229
+ const childId = armStepId(arm);
2931
3230
  addNode(childId, {
2932
- kind: SINGLE_STEP_KINDS[arm.type],
3231
+ kind: armStepKind(arm),
2933
3232
  dependsOn: prevTails,
2934
3233
  downstream: [],
2935
3234
  unsatisfiedDeps: prevTails.length,
@@ -2967,9 +3266,9 @@ function compilePlan(g) {
2967
3266
  node.otherwise
2968
3267
  ] : node.steps;
2969
3268
  const childIds = arms.map((arm) => {
2970
- const childId = arm.type === "mapping" ? arm.id : singleStepId(arm);
3269
+ const childId = arm.type === "mapping" ? arm.id : armStepId(arm);
2971
3270
  addNode(childId, {
2972
- kind: arm.type === "mapping" ? "map" : SINGLE_STEP_KINDS[arm.type],
3271
+ kind: arm.type === "mapping" ? "map" : armStepKind(arm),
2973
3272
  dependsOn: [
2974
3273
  entryId
2975
3274
  ],
@@ -3343,7 +3642,16 @@ var WorkflowTemplateError = class extends Error {
3343
3642
  this.name = "WorkflowTemplateError";
3344
3643
  }
3345
3644
  };
3645
+ function isMapConfigObject(v) {
3646
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3647
+ }
3648
+ __name(isMapConfigObject, "isMapConfigObject");
3649
+ __name3(isMapConfigObject, "isMapConfigObject");
3346
3650
  function parseMapConfig(raw, stepId) {
3651
+ if (isMapConfigObject(raw)) return raw;
3652
+ if (typeof raw !== "string") {
3653
+ throw new Error(`Stored mapping step "${stepId}" has a mapConfig that is neither a JSON string nor an object.`);
3654
+ }
3347
3655
  try {
3348
3656
  return JSON.parse(raw);
3349
3657
  } catch (e) {
@@ -3352,6 +3660,13 @@ function parseMapConfig(raw, stepId) {
3352
3660
  }
3353
3661
  __name(parseMapConfig, "parseMapConfig");
3354
3662
  __name3(parseMapConfig, "parseMapConfig");
3663
+ function mapConfigWire(raw) {
3664
+ if (typeof raw === "string") return raw;
3665
+ if (isMapConfigObject(raw)) return canonicalJson(raw);
3666
+ return void 0;
3667
+ }
3668
+ __name(mapConfigWire, "mapConfigWire");
3669
+ __name3(mapConfigWire, "mapConfigWire");
3355
3670
  var TEMPLATE_PLACEHOLDER = /\$\{([^}]*)\}/g;
3356
3671
  var TEMPLATE_NAMESPACES = [
3357
3672
  "initData",
@@ -3622,6 +3937,7 @@ function isContinuedFailureValue(v) {
3622
3937
  }
3623
3938
  __name(isContinuedFailureValue, "isContinuedFailureValue");
3624
3939
  __name3(isContinuedFailureValue, "isContinuedFailureValue");
3940
+ var isHitlNode2 = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
3625
3941
  function lowerContainerArm(mapping, step22) {
3626
3942
  const stepId = nodeIdOf(step22);
3627
3943
  return {
@@ -3707,7 +4023,28 @@ function resolvePlacements(calls) {
3707
4023
  break;
3708
4024
  }
3709
4025
  });
3710
- const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping) => {
4026
+ const hitlPlacementIssue = /* @__PURE__ */ __name3((node, ref, i, container) => {
4027
+ if (!isHitlNode2(node)) return void 0;
4028
+ const id = node.id;
4029
+ if (ref.armMap) {
4030
+ return {
4031
+ code: "node-type-unsupported-in-container",
4032
+ message: workflowHitlArmShapeMessage(node.type, id, "mapped-arm"),
4033
+ callIndex: i,
4034
+ stepId: id
4035
+ };
4036
+ }
4037
+ if (container !== "place" && !workflowContainerRunsHitlArm(container)) {
4038
+ return {
4039
+ code: "node-type-unsupported-in-container",
4040
+ message: workflowHitlArmUnsupportedMessage(node.type, id, container),
4041
+ callIndex: i,
4042
+ stepId: id
4043
+ };
4044
+ }
4045
+ return void 0;
4046
+ }, "hitlPlacementIssue");
4047
+ const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
3711
4048
  if ("node" in ref) {
3712
4049
  if (ref.node.type === "mapping" && !allowMapping) {
3713
4050
  issues.push({
@@ -3724,7 +4061,7 @@ function resolvePlacements(calls) {
3724
4061
  if (!d) {
3725
4062
  issues.push({
3726
4063
  code: "unknown-step-ref",
3727
- message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)`,
4064
+ message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)/approval(\u2026)/waitForSignal(\u2026)`,
3728
4065
  callIndex: i,
3729
4066
  stepId: ref.ref
3730
4067
  });
@@ -3739,6 +4076,11 @@ function resolvePlacements(calls) {
3739
4076
  });
3740
4077
  return void 0;
3741
4078
  }
4079
+ const hitl = hitlPlacementIssue(d.node, ref, i, container);
4080
+ if (hitl) {
4081
+ issues.push(hitl);
4082
+ return void 0;
4083
+ }
3742
4084
  const prior = placedBy.get(ref.ref);
3743
4085
  if (prior !== void 0 && prior !== i) {
3744
4086
  issues.push({
@@ -3752,23 +4094,31 @@ function resolvePlacements(calls) {
3752
4094
  placedBy.set(ref.ref, i);
3753
4095
  return d.node;
3754
4096
  }, "resolve");
4097
+ const claim = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
4098
+ if ("ref" in ref) {
4099
+ resolve(ref, i, allowMapping, container);
4100
+ return;
4101
+ }
4102
+ const hitl = hitlPlacementIssue(ref.node, ref, i, container);
4103
+ if (hitl) issues.push(hitl);
4104
+ }, "claim");
3755
4105
  calls.forEach((call, i) => {
3756
4106
  switch (call.kind) {
3757
4107
  case "place":
3758
- resolve({
4108
+ claim({
3759
4109
  ref: call.ref
3760
- }, i, false);
4110
+ }, i, false, "place");
3761
4111
  break;
3762
4112
  case "parallel":
3763
- for (const arm of call.arms) if ("ref" in arm) resolve(arm, i, false);
4113
+ for (const arm of call.arms) claim(arm, i, false, "parallel");
3764
4114
  break;
3765
4115
  case "conditional":
3766
- for (const a of call.arms) if ("ref" in a.target) resolve(a.target, i, true);
3767
- if (call.otherwise && "ref" in call.otherwise) resolve(call.otherwise, i, true);
4116
+ for (const a of call.arms) claim(a.target, i, true, "conditional");
4117
+ if (call.otherwise) claim(call.otherwise, i, true, "conditional");
3768
4118
  break;
3769
4119
  case "foreach":
3770
4120
  case "loop":
3771
- if ("ref" in call.body) resolve(call.body, i, false);
4121
+ claim(call.body, i, false, call.kind);
3772
4122
  break;
3773
4123
  default:
3774
4124
  break;
@@ -3777,7 +4127,7 @@ function resolvePlacements(calls) {
3777
4127
  const graph = [];
3778
4128
  const lookup = /* @__PURE__ */ __name3((ref) => {
3779
4129
  const n2 = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3780
- if (!n2 || !ref.armMap || n2.type === "mapping") return n2;
4130
+ if (!n2 || !ref.armMap || n2.type === "mapping" || isHitlNode2(n2)) return n2;
3781
4131
  return lowerContainerArm(ref.armMap, n2);
3782
4132
  }, "lookup");
3783
4133
  calls.forEach((call, i) => {
@@ -3859,6 +4209,75 @@ function resolvePlacements(calls) {
3859
4209
  }
3860
4210
  __name(resolvePlacements, "resolvePlacements");
3861
4211
  __name3(resolvePlacements, "resolvePlacements");
4212
+ var GOAL_JUDGE_STEP_ID = "__goal_judge";
4213
+ var NON_LEAF_KINDS = /* @__PURE__ */ new Set([
4214
+ "foreach",
4215
+ "branch"
4216
+ ]);
4217
+ var CONDITIONAL_JOIN_ID = /^conditional@\d+\.join$/;
4218
+ function isConditionalJoinId(stepId) {
4219
+ return CONDITIONAL_JOIN_ID.test(stepId);
4220
+ }
4221
+ __name(isConditionalJoinId, "isConditionalJoinId");
4222
+ __name3(isConditionalJoinId, "isConditionalJoinId");
4223
+ function isPlainObject(v) {
4224
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4225
+ }
4226
+ __name(isPlainObject, "isPlainObject");
4227
+ __name3(isPlainObject, "isPlainObject");
4228
+ function leafValue(row) {
4229
+ if (row.status === "completed") return row.output === void 0 ? null : row.output;
4230
+ return continuedFailureValue(row.error, row.killReason);
4231
+ }
4232
+ __name(leafValue, "leafValue");
4233
+ __name3(leafValue, "leafValue");
4234
+ function runOutputLeaves(steps) {
4235
+ const dependedOn = /* @__PURE__ */ new Set();
4236
+ for (const s of steps) {
4237
+ if (s.stepId === GOAL_JUDGE_STEP_ID) continue;
4238
+ for (const d of s.dependsOn ?? []) dependedOn.add(d);
4239
+ }
4240
+ return steps.filter((s) => s.stepId !== GOAL_JUDGE_STEP_ID && !NON_LEAF_KINDS.has(s.kind ?? "") && s.foreachIndex === void 0 && s.loopParentId === void 0 && s.status !== "skipped" && !dependedOn.has(s.stepId));
4241
+ }
4242
+ __name(runOutputLeaves, "runOutputLeaves");
4243
+ __name3(runOutputLeaves, "runOutputLeaves");
4244
+ function deriveRunOutput(steps) {
4245
+ const leaves = runOutputLeaves(steps);
4246
+ if (leaves.length === 0) return void 0;
4247
+ if (leaves.length === 1) {
4248
+ const leaf = leaves[0];
4249
+ const value22 = leafValue(leaf);
4250
+ if (isConditionalJoinId(leaf.stepId) && isPlainObject(value22)) {
4251
+ const keys = Object.keys(value22);
4252
+ if (keys.length === 1) return {
4253
+ output: value22[keys[0]],
4254
+ leafIds: [
4255
+ keys[0]
4256
+ ]
4257
+ };
4258
+ }
4259
+ return {
4260
+ output: value22,
4261
+ leafIds: [
4262
+ leaf.stepId
4263
+ ]
4264
+ };
4265
+ }
4266
+ const output = {};
4267
+ for (const leaf of leaves) output[leaf.stepId] = leafValue(leaf);
4268
+ return {
4269
+ output,
4270
+ leafIds: leaves.map((l) => l.stepId)
4271
+ };
4272
+ }
4273
+ __name(deriveRunOutput, "deriveRunOutput");
4274
+ __name3(deriveRunOutput, "deriveRunOutput");
4275
+ function subrunSettledOutput(child) {
4276
+ if (child.output !== void 0) return child.output;
4277
+ return deriveRunOutput(child.steps ?? [])?.output ?? null;
4278
+ }
4279
+ __name(subrunSettledOutput, "subrunSettledOutput");
4280
+ __name3(subrunSettledOutput, "subrunSettledOutput");
3862
4281
  function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3863
4282
  const byId = /* @__PURE__ */ new Map();
3864
4283
  for (const s of steps) {
@@ -4027,6 +4446,15 @@ function replayLedger(g, ledger) {
4027
4446
  local,
4028
4447
  diverged: recordedCount !== local
4029
4448
  });
4449
+ } else if (node.kind === "subrun" && recorded.status === "completed" && recorded.child) {
4450
+ const local = subrunSettledOutput(recorded.child);
4451
+ verdicts.push({
4452
+ stepId: id,
4453
+ kind: "subrun",
4454
+ recorded: recorded.output,
4455
+ local,
4456
+ diverged: canonical(recorded.output) !== canonical(local)
4457
+ });
4030
4458
  }
4031
4459
  }
4032
4460
  return {
@@ -4214,7 +4642,23 @@ function runCancelView(cancel) {
4214
4642
  return {
4215
4643
  requestedAt: cancel.requestedAt,
4216
4644
  requestedBy: cancel.requestedBy ?? "",
4217
- forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS
4645
+ forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS,
4646
+ // LUA-686: the wall's own request (LUA-683 stamps `wall:true` from a system actor only) rides the wire, so a
4647
+ // client can tell a wall-ended `timed_out` run's audit block from a cancel it should chip. Absent, never false.
4648
+ ...cancel.wall === true ? {
4649
+ wall: true
4650
+ } : {},
4651
+ // LUA-704: a forced terminal's audit — who forced it, when, and a human's own reason text (the run's `reason`
4652
+ // is the terminal's). Absent on an unforced terminal, never null.
4653
+ ...typeof cancel.forcedAt === "number" ? {
4654
+ forcedAt: cancel.forcedAt
4655
+ } : {},
4656
+ ...typeof cancel.forcedBy === "string" && cancel.forcedBy ? {
4657
+ forcedBy: cancel.forcedBy
4658
+ } : {},
4659
+ ...typeof cancel.forceReason === "string" && cancel.forceReason ? {
4660
+ forceReason: cancel.forceReason
4661
+ } : {}
4218
4662
  };
4219
4663
  }
4220
4664
  __name(runCancelView, "runCancelView");
@@ -4237,7 +4681,6 @@ __name3(runWorkspaceView, "runWorkspaceView");
4237
4681
  function toWorkflowRunSummary(run) {
4238
4682
  const status = run.status;
4239
4683
  const principal = run.principal?.principal;
4240
- const gated = status === "gated" || status === "suspended";
4241
4684
  return pruneUndefined({
4242
4685
  runId: run.id,
4243
4686
  workflowId: run.workflowId,
@@ -4249,7 +4692,14 @@ function toWorkflowRunSummary(run) {
4249
4692
  orgId: run.orgId,
4250
4693
  spaceAgentId: run.spaceAgentId,
4251
4694
  status,
4252
- gate: gated ? run.gate : void 0,
4695
+ // LUA-702 (pass-3B D6): the gate rides the wire whenever a NON-terminal doc carries one. In the LUA-681 window
4696
+ // (an approval resolved beside an open exception park) the run is `running` AND still holds `gate{exception}`
4697
+ // until the tick tail parks it back; projecting it only on gated|suspended hid that park from R4 / the CLI /
4698
+ // the desktop (prod 2026-09-05: `status: running, gate: null`). A terminal doc carries no gate by contract
4699
+ // (`terminalizeRun` drops `gate` / `suspendedFor` in every terminal write), but a row terminalized before that
4700
+ // held — pre-LUA-677/694 docs, a script-tier backstop that passed no `unset` — may still carry a stale one:
4701
+ // hidden here, so a `failed` run never reads "Needs a decision" on R3/R4 or the CLI (#2476 review).
4702
+ gate: run.gate && !isTerminalRunStatus(status) ? run.gate : void 0,
4253
4703
  batchId: run.batchId,
4254
4704
  goalId: run.goalId,
4255
4705
  foreachOverflow: run.foreachOverflow,
@@ -4272,9 +4722,11 @@ function toWorkflowRunSummary(run) {
4272
4722
  principalKind: run.principalKind ?? "user",
4273
4723
  cancel: runCancelView(run.cancel),
4274
4724
  usage: runUsage(run),
4725
+ // LUA-697: a row persisted before the write seams (#2406 / #2465 / the script tier) leaves scrubbed here too —
4726
+ // idempotent on a scrubbed message, bounded input; an empty message falls back to the code.
4275
4727
  error: run.error ? {
4276
4728
  code: run.error.code ?? "error",
4277
- message: run.error.message ?? "",
4729
+ message: scrubStepErrorMessage(run.error.message) ?? run.error.code ?? "error",
4278
4730
  stepId: run.error.stepId
4279
4731
  } : void 0,
4280
4732
  kind: "run",
@@ -4295,6 +4747,86 @@ function toWorkflowRunSummary(run) {
4295
4747
  }
4296
4748
  __name(toWorkflowRunSummary, "toWorkflowRunSummary");
4297
4749
  __name3(toWorkflowRunSummary, "toWorkflowRunSummary");
4750
+ var STEP_ERROR_DETAIL_KEYS = [
4751
+ "reason",
4752
+ "key",
4753
+ "integrationType",
4754
+ "candidates",
4755
+ "connectionId",
4756
+ "status",
4757
+ "code",
4758
+ "providerStatus",
4759
+ "model",
4760
+ "turnIndex",
4761
+ "message",
4762
+ // LUA-655: `output_schema_invalid` — the Ajv issues ({path, message}, ≤ 20, scrubbed at the source too) and the
4763
+ // in-session repair rounds the attempt spent.
4764
+ "issues",
4765
+ "repairRounds",
4766
+ // LUA-669 (#2446 review 4): the subrun failures — `subrun_<status>` names the child run and how it ended
4767
+ // (`childStatus`, `childReason: 'max_duration'` under `subrun_timed_out`), `subrun_depth_exceeded` its `depth` /
4768
+ // `max`, and every refusal the target `workflowId`. Short scalars only: the child's whole `childError` and the
4769
+ // cycle walk's `ancestors` stay off the wire — the child run's own R4 carries its error.
4770
+ "childRunId",
4771
+ "childStatus",
4772
+ "childReason",
4773
+ "max",
4774
+ "depth",
4775
+ "workflowId",
4776
+ // LUA-696 (review 2): the `ctx.once` key of an `effect_in_doubt` park — the step site stamps it here (scrubbed)
4777
+ // beside `park.effectKey`; a key is user text and leaves scrubbed like every other string leaf.
4778
+ "effectKey"
4779
+ ];
4780
+ var STEP_ERROR_DETAIL_MAX_BYTES = 8 * 1024;
4781
+ var DETAIL_MAX_DEPTH = 4;
4782
+ var DETAIL_MAX_ITEMS = 100;
4783
+ function scrubDetailValue(value22, depth) {
4784
+ if (typeof value22 === "string") return scrubSecretText(value22);
4785
+ if (typeof value22 === "number" || typeof value22 === "boolean" || value22 === null) return value22;
4786
+ if (depth >= DETAIL_MAX_DEPTH) return void 0;
4787
+ if (Array.isArray(value22)) {
4788
+ return value22.slice(0, DETAIL_MAX_ITEMS).map((v) => scrubDetailValue(v, depth + 1)).filter((v) => v !== void 0);
4789
+ }
4790
+ if (typeof value22 === "object") {
4791
+ const out = {};
4792
+ for (const [k, v] of Object.entries(value22)) {
4793
+ const s = scrubDetailValue(v, depth + 1);
4794
+ if (s !== void 0) out[k] = s;
4795
+ }
4796
+ return out;
4797
+ }
4798
+ return void 0;
4799
+ }
4800
+ __name(scrubDetailValue, "scrubDetailValue");
4801
+ __name3(scrubDetailValue, "scrubDetailValue");
4802
+ function stepErrorDetail(error) {
4803
+ if (!error || typeof error !== "object") return void 0;
4804
+ const d = error.detail;
4805
+ if (!d || typeof d !== "object" || Array.isArray(d)) return void 0;
4806
+ const bag = d;
4807
+ const out = {};
4808
+ for (const k of STEP_ERROR_DETAIL_KEYS) {
4809
+ if (!(k in bag)) continue;
4810
+ const v = scrubDetailValue(bag[k], 1);
4811
+ if (v !== void 0) out[k] = v;
4812
+ }
4813
+ if (!Object.keys(out).length) return void 0;
4814
+ let bytes;
4815
+ try {
4816
+ bytes = new TextEncoder().encode(JSON.stringify(out)).length;
4817
+ } catch {
4818
+ return void 0;
4819
+ }
4820
+ if (bytes <= STEP_ERROR_DETAIL_MAX_BYTES) return out;
4821
+ const scalars = Object.fromEntries(Object.entries(out).filter(([, v]) => v === null || typeof v !== "object"));
4822
+ return {
4823
+ ...scalars,
4824
+ __truncated: true,
4825
+ bytes
4826
+ };
4827
+ }
4828
+ __name(stepErrorDetail, "stepErrorDetail");
4829
+ __name3(stepErrorDetail, "stepErrorDetail");
4298
4830
  var MAX_HOLIDAYS = 366;
4299
4831
  var MAX_WALK_DAYS = 400;
4300
4832
  var HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
@@ -4903,6 +5435,34 @@ var ApprovalOnTimeoutSchema = z22.union([
4903
5435
  EscalationHopSchema
4904
5436
  ])).min(1).max(ESCALATION_MAX_HOPS + 1)
4905
5437
  ]);
5438
+ var APPROVER_SPEC_SHAPES = [
5439
+ "'creator'",
5440
+ "'org-admins'",
5441
+ "{users:[userId, \u2026]}",
5442
+ "{role:roleName}",
5443
+ "{group:groupName}",
5444
+ "{governance:{policyId}}"
5445
+ ];
5446
+ var APPROVER_WRITTEN_MAX = 120;
5447
+ var USER_ID_SHAPED_RE = /^[^\s@]{1,128}$/;
5448
+ function describeApproverSpecRefusal(spec) {
5449
+ const raw = spec === void 0 ? "undefined" : JSON.stringify(spec) ?? String(spec);
5450
+ const written = raw.length > APPROVER_WRITTEN_MAX ? `${raw.slice(0, APPROVER_WRITTEN_MAX - 1)}\u2026` : raw;
5451
+ const users = typeof spec === "object" && spec !== null ? spec.users : void 0;
5452
+ const approver = typeof users === "string" && USER_ID_SHAPED_RE.test(users) ? {
5453
+ users: [
5454
+ users
5455
+ ]
5456
+ } : "creator";
5457
+ const message = `approver ${written} is not an approver \u2014 legal: ${APPROVER_SPEC_SHAPES.join(" | ")}. 'creator' is the person who started the run: write approver:'creator' for "ask me" / "I approve"; {users:[\u2026]} takes user ids, never emails, names or {type:'user'}` + (approver === "creator" ? "" : `; here: approver:${JSON.stringify(approver)}`);
5458
+ return {
5459
+ approver,
5460
+ written,
5461
+ message
5462
+ };
5463
+ }
5464
+ __name(describeApproverSpecRefusal, "describeApproverSpecRefusal");
5465
+ __name3(describeApproverSpecRefusal, "describeApproverSpecRefusal");
4906
5466
  var BINDING_ROOTS = [
4907
5467
  "initData",
4908
5468
  "stepResults",
@@ -4937,7 +5497,19 @@ function validateApproverBlock(node, opts = {
4937
5497
  if (!r.success) {
4938
5498
  const users = spec?.users;
4939
5499
  if (Array.isArray(users) && users.length > APPROVER_SPEC_MAX_USERS) push("cap-exceeded", path, `at most ${APPROVER_SPEC_MAX_USERS} users`);
4940
- else push("approver-invalid", path, r.error.issues[0]?.message ?? "invalid approver");
5500
+ else {
5501
+ const refusal = describeApproverSpecRefusal(spec);
5502
+ issues.push({
5503
+ code: "approver-invalid",
5504
+ path,
5505
+ severity: "error",
5506
+ message: refusal.message,
5507
+ repair: {
5508
+ approver: refusal.approver,
5509
+ written: refusal.written
5510
+ }
5511
+ });
5512
+ }
4941
5513
  return;
4942
5514
  }
4943
5515
  const s = r.data;
@@ -5449,7 +6021,7 @@ var assertPredicate = /* @__PURE__ */ __name((p, where) => {
5449
6021
  }, "assertPredicate");
5450
6022
  var assertRetry = /* @__PURE__ */ __name((r, id) => {
5451
6023
  if (!r) return;
5452
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.backoff must be 'fixed' | 'exponential'`);
6024
+ if (r.backoff !== void 0 && !WORKFLOW_RETRY_BACKOFFS.includes(r.backoff)) throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.backoff must be ${WORKFLOW_RETRY_BACKOFFS.map((b) => `'${b}'`).join(" | ")}`);
5453
6025
  if (r.maxBackoffSeconds !== void 0) {
5454
6026
  if (r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.maxBackoffSeconds is only meaningful with backoff:'exponential'`);
5455
6027
  if (r.maxBackoffSeconds <= 0 || r.backoffSeconds !== void 0 && r.maxBackoffSeconds < r.backoffSeconds) throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.maxBackoffSeconds must be > 0 and \u2265 backoffSeconds`);
@@ -5637,8 +6209,14 @@ function materializeEntry(entry, steps) {
5637
6209
  }
5638
6210
  }
5639
6211
  __name(materializeEntry, "materializeEntry");
6212
+ var isHitlEntry = /* @__PURE__ */ __name((n2) => isWorkflowHitlEntryType(n2?.type), "isHitlEntry");
5640
6213
  function graphHasHitl(graph, steps) {
5641
- if (graph.some((e) => e.type === "approval" || e.type === "waitForSignal")) return true;
6214
+ for (const e of graph) {
6215
+ if (isHitlEntry(e)) return true;
6216
+ if (e.type === "parallel" && e.steps.some(isHitlEntry)) return true;
6217
+ if (e.type === "conditional" && (e.steps.some(isHitlEntry) || isHitlEntry(e.otherwise))) return true;
6218
+ if ((e.type === "foreach" || e.type === "loop") && isHitlEntry(e.step)) return true;
6219
+ }
5642
6220
  return Object.values(steps).some((s) => s.suspendSchema !== void 0);
5643
6221
  }
5644
6222
  __name(graphHasHitl, "graphHasHitl");
@@ -5693,7 +6271,6 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5693
6271
  const [mapping, target] = arm;
5694
6272
  if (target === void 0 || arm.length < 2) throw new LuaWorkflowBuildError("container-arm-empty", `${where}: a bare mapping arm has nothing to run`);
5695
6273
  if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) throw new LuaWorkflowBuildError("mapping-placement", `${where}: the arm head must be a map config object`);
5696
- if (mapping.type === "approval" || target.type === "approval") throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
5697
6274
  assertNoClosure(mapping, `${where} arm map`);
5698
6275
  this.recordEnvRefs(mapping);
5699
6276
  const inner = this.armRef(target, where);
@@ -5711,8 +6288,9 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5711
6288
  if (typeof arm === "string") return {
5712
6289
  ref: arm
5713
6290
  };
5714
- if (arm && typeof arm === "object" && arm.type === "approval") {
5715
- throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
6291
+ if (arm && typeof arm === "object" && isHitlEntry(arm)) {
6292
+ const id = arm.id;
6293
+ throw new LuaWorkflowBuildError("invalid-step", `${where}: an approval / waitForSignal arm is placed by id \u2014 declare it with .approval(${JSON.stringify(id ?? "id")}, \u2026) / .waitForSignal(\u2026) and pass the id string`);
5716
6294
  }
5717
6295
  return {
5718
6296
  node: this.registerStep(arm, where)
@@ -5913,17 +6491,13 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5913
6491
  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'`);
5914
6492
  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`);
5915
6493
  if (opts.toolScope?.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${id}": toolScope.jobTools require tier:'job' (job-tools-require-job-tier)`);
5916
- if (opts.maxTurns !== void 0) {
5917
- if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxTurns is only legal on a tier:'job' agent step`);
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`);
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`);
6494
+ for (const m of WORKFLOW_JOB_RANGE_MEMBERS) {
6495
+ if (opts[m] === void 0) continue;
6496
+ if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": ${m} is only legal on a tier:'job' agent step`);
6497
+ if (!isWithinWorkflowJobRange(m, opts[m])) {
6498
+ const { min, max } = WORKFLOW_JOB_RANGES[m];
6499
+ throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": ${m} must be an integer ${min}..${max}`);
6500
+ }
5927
6501
  }
5928
6502
  assertTimeout({
5929
6503
  id,
@@ -6075,8 +6649,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6075
6649
  itemTimeout: opts.itemTimeout
6076
6650
  });
6077
6651
  return this.push({
6078
- kind: "entry",
6079
- entry: node
6652
+ kind: "declare",
6653
+ node
6080
6654
  });
6081
6655
  }
6082
6656
  waitForSignal(id, opts) {
@@ -6094,12 +6668,13 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6094
6668
  acceptedSources: opts.acceptedSources
6095
6669
  });
6096
6670
  return this.push({
6097
- kind: "entry",
6098
- entry: node
6671
+ kind: "declare",
6672
+ node
6099
6673
  });
6100
6674
  }
6101
6675
  workflow(id, ref, input, opts) {
6102
6676
  this.assertId(id, "workflow()");
6677
+ assertRetry(opts?.retry, id);
6103
6678
  const name = typeof ref === "string" ? ref : ref instanceof LuaWorkflow ? ref.getName() : void 0;
6104
6679
  if (!name) throw new LuaWorkflowBuildError("invalid-envelope", `workflow("${id}") needs a LuaWorkflow or a workflow name`);
6105
6680
  if (opts?.workspace === "inherit") {
@@ -6121,7 +6696,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6121
6696
  id,
6122
6697
  workflowId: name,
6123
6698
  input,
6124
- workspace: opts?.workspace
6699
+ workspace: opts?.workspace,
6700
+ retry: opts?.retry
6125
6701
  });
6126
6702
  return this.push({
6127
6703
  kind: "declare",
@@ -6135,7 +6711,7 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6135
6711
  const { graph, issues } = resolvePlacements(this.calls);
6136
6712
  const fatal = issues[0];
6137
6713
  if (fatal) {
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;
6714
+ const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)/approval(\u2026)/waitForSignal(\u2026) somewhere in the chain \u2014 before OR after the reference" : void 0;
6139
6715
  throw new LuaWorkflowBuildError(fatal.code, fatal.message, hint);
6140
6716
  }
6141
6717
  if (graph.length === 0) throw new LuaWorkflowBuildError("empty-graph", `workflow "${this.config.name}" has no entries`);