@mnemom/mnemom 0.17.34-next.2 → 0.17.34-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/commands/agent-run.d.ts +16 -6
  2. package/dist/commands/agent-run.js +52 -27
  3. package/dist/commands/agent-share-test-issuer.d.ts +67 -0
  4. package/dist/commands/agent-share-test-issuer.js +139 -0
  5. package/dist/commands/agent-share.d.ts +7 -1
  6. package/dist/commands/agent-share.js +14 -4
  7. package/dist/commands/agent.d.ts +7 -5
  8. package/dist/commands/agent.js +84 -45
  9. package/dist/commands/durable-lock.d.ts +8 -1
  10. package/dist/commands/durable-lock.js +9 -4
  11. package/dist/commands/scan.d.ts +12 -1
  12. package/dist/commands/scan.js +22 -3
  13. package/dist/commands/wrap.js +1 -1
  14. package/dist/durable/adopt-live.js +5 -8
  15. package/dist/durable/adopt.d.ts +8 -0
  16. package/dist/durable/adopt.js +27 -6
  17. package/dist/durable/cwd-rule.d.ts +9 -1
  18. package/dist/durable/cwd-rule.js +18 -0
  19. package/dist/durable/launch-spec.d.ts +2 -0
  20. package/dist/durable/launch-spec.js +1 -1
  21. package/dist/durable/test-mode.mjs +25 -1
  22. package/dist/host/client.d.ts +3 -1
  23. package/dist/host/daemon.d.ts +7 -3
  24. package/dist/host/daemon.js +13 -3
  25. package/dist/host/owner.d.ts +3 -0
  26. package/dist/host/owner.js +2 -0
  27. package/dist/host/run.d.ts +2 -3
  28. package/dist/host/run.js +5 -20
  29. package/dist/host/s1-names.d.ts +1 -0
  30. package/dist/host/s1-names.js +1 -0
  31. package/dist/host/verbs.d.ts +2 -0
  32. package/dist/host/verbs.js +11 -0
  33. package/dist/index.js +13 -9
  34. package/dist/lib/agent.d.ts +5 -3
  35. package/dist/lib/agent.js +6 -3
  36. package/dist/lib/claude-bin.d.ts +6 -0
  37. package/dist/lib/claude-bin.js +26 -0
  38. package/dist/lib/commit-report.d.ts +5 -4
  39. package/dist/lib/commit-report.js +6 -9
  40. package/dist/lib/config.d.ts +8 -1
  41. package/dist/lib/config.js +28 -0
  42. package/dist/lib/durable/launch.d.ts +28 -11
  43. package/dist/lib/durable/launch.js +33 -15
  44. package/dist/lib/durable/live.d.ts +15 -19
  45. package/dist/lib/durable/live.js +41 -43
  46. package/dist/lib/launcher-config.d.ts +6 -0
  47. package/dist/lib/launcher-config.js +11 -0
  48. package/dist/lib/runner/exec-append.d.ts +15 -0
  49. package/dist/lib/runner/exec-append.js +2 -0
  50. package/dist/lib/runner/executor-client.d.ts +2 -1
  51. package/dist/lib/runner/executor-state.d.ts +10 -0
  52. package/dist/lib/runner/executor-state.js +24 -0
  53. package/dist/lib/runner/github-base.d.ts +24 -0
  54. package/dist/lib/runner/github-base.js +27 -0
  55. package/dist/lib/runner/job.d.ts +11 -4
  56. package/dist/lib/runner/job.js +59 -16
  57. package/dist/lib/runner/oidc.d.ts +3 -2
  58. package/dist/lib/runner/oidc.js +7 -4
  59. package/dist/lib/runner/options.d.ts +6 -0
  60. package/dist/lib/runner/options.js +19 -1
  61. package/dist/lib/runner/outcome.js +2 -3
  62. package/dist/lib/runner/phases.d.ts +7 -4
  63. package/dist/lib/runner/phases.js +8 -4
  64. package/dist/lib/runner/proxy.d.ts +5 -1
  65. package/dist/lib/runner/proxy.js +7 -1
  66. package/dist/lib/scan/cost-per-pr.d.ts +93 -0
  67. package/dist/lib/scan/cost-per-pr.js +101 -0
  68. package/dist/lib/scan/record.d.ts +27 -1
  69. package/dist/lib/scan/record.js +114 -16
  70. package/dist/lib/scan/report-email.d.ts +25 -5
  71. package/dist/lib/scan/report-email.js +23 -2
  72. package/dist/lib/scan/scan.d.ts +3 -0
  73. package/dist/lib/scan/scan.js +13 -1
  74. package/dist/lib/session-key.d.ts +4 -3
  75. package/dist/vendor/durable-contract/index.js +207 -169
  76. package/dist/vendor/shared/durable-contract/doors.d.ts +26 -1
  77. package/dist/vendor/shared/durable-contract/doors.js +37 -1
  78. package/dist/vendor/shared/durable-contract/events.d.ts +1 -1
  79. package/dist/vendor/shared/durable-contract/events.js +13 -3
  80. package/dist/vendor/shared/durable-contract/freeze-record.d.ts +2 -2
  81. package/dist/vendor/shared/durable-contract/schema.d.ts +5 -0
  82. package/dist/vendor/shared/durable-contract/schema.js +6 -0
  83. package/dist/vendor/shared/durable-contract/states.d.ts +2 -1
  84. package/dist/vendor/shared/durable-contract/states.js +2 -1
  85. package/package.json +1 -1
  86. package/dist/lib/durable/first-turn.d.ts +0 -50
  87. package/dist/lib/durable/first-turn.js +0 -102
@@ -199,7 +199,7 @@ var SUSPENDED_REASONS = [
199
199
  "approval_denied"
200
200
  ];
201
201
  var PAUSED_REASONS = ["owner", "agent_contained", "breakglass"];
202
- var FAILED_REASONS = ["transcript_lost"];
202
+ var FAILED_REASONS = ["transcript_lost", "runner_failed"];
203
203
  var KILLED_REASONS = ["agent_killed"];
204
204
  var STATE_REASONS = {
205
205
  resuming: [null],
@@ -2034,6 +2034,183 @@ function grantWarningDays(remainingSeconds, warningWindowSeconds) {
2034
2034
  return Math.ceil(remainingSeconds / 86400);
2035
2035
  }
2036
2036
 
2037
+ // ../shared/durable-contract/src/doors.ts
2038
+ var DURABLE_POOL_CLAIM_FLAG = "DURABLE_POOL_CLAIM";
2039
+ function poolClaimEnabled(flagValue, orgId) {
2040
+ return poolClaimOrgs(flagValue).includes(orgId);
2041
+ }
2042
+ function isPoolSession(s, flagValue) {
2043
+ return s.home_executor_id === null && s.durable_mode === "locked" && poolClaimEnabled(flagValue, s.org_id);
2044
+ }
2045
+ function poolClaimOrgs(flagValue) {
2046
+ if (flagValue === void 0) return [];
2047
+ const orgs = flagValue.split(",").map((s) => s.trim());
2048
+ if (orgs.some((o) => !ID_RE.test(o))) return [];
2049
+ return [...new Set(orgs)];
2050
+ }
2051
+ var EXECUTOR_SCOPES = ["user", "org"];
2052
+ var ORG_SCOPE_EXECUTOR_KINDS = ["container", "fleet"];
2053
+ var OIDC_EXCHANGE_REFUSALS = [
2054
+ "bad_issuer",
2055
+ "repo_not_enrolled",
2056
+ "ref_not_allowed",
2057
+ "pool_flag_off"
2058
+ ];
2059
+ var OIDC_EXCHANGE_AUDIENCE = "mnemom-executor";
2060
+ var ORG_POLICY_LIMIT_KEYS = ["per_session_budget_usd", "live_lease_cap"];
2061
+ var LIVE_LEASE_CAP_MIN = 1;
2062
+ var LIVE_LEASE_CAP_MAX = 1e3;
2063
+ var ORG_CAP_LOCK_PREFIX = "durable_org_cap:";
2064
+ var DEFAULT_LEASE_CAP_ENV = "DURABLE_DEFAULT_LEASE_CAP";
2065
+ var capOk = (v) => Number.isInteger(v) && v >= LIVE_LEASE_CAP_MIN && v <= LIVE_LEASE_CAP_MAX;
2066
+ function parseDefaultLeaseCap(envValue) {
2067
+ if (envValue === void 0 || !/^[1-9][0-9]{0,3}$/.test(envValue)) return null;
2068
+ const n = Number(envValue);
2069
+ return capOk(n) ? n : null;
2070
+ }
2071
+ function effectiveLeaseCap(orgLimits, defaultRowLimits, defaultCap) {
2072
+ const present = [orgLimits?.live_lease_cap, defaultRowLimits?.live_lease_cap, defaultCap].filter(
2073
+ (v) => v !== void 0 && v !== null
2074
+ );
2075
+ for (const v of present)
2076
+ if (!capOk(v)) throw new RangeError(`live lease cap out of range: ${String(v)}`);
2077
+ if (present.length === 0)
2078
+ throw new RangeError("no live lease cap: DURABLE_DEFAULT_LEASE_CAP is unset");
2079
+ return present[0];
2080
+ }
2081
+ var DISPATCH_SESSION_CID_PREFIX = "dispatch-";
2082
+ var DISPATCH_SESSION_NAME_PREFIX = "job-";
2083
+ var CREATE_READY_SESSION_REFUSALS = [
2084
+ "no_dispatch_agent",
2085
+ "seal_not_found",
2086
+ "cell_unmapped",
2087
+ // 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
2088
+ "charter_mismatch"
2089
+ ];
2090
+ var CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
2091
+ var CREATE_READY_SESSION_ANSWERS = Object.freeze({
2092
+ created: ["outcome", "session_id"],
2093
+ existing: ["outcome", "session_id"],
2094
+ refused: ["outcome", "reason"]
2095
+ });
2096
+ function createReadySessionAnswerOk(v) {
2097
+ if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
2098
+ const a = v;
2099
+ if (typeof a.outcome !== "string" || !Object.prototype.hasOwnProperty.call(CREATE_READY_SESSION_ANSWERS, a.outcome))
2100
+ return false;
2101
+ const keys = CREATE_READY_SESSION_ANSWERS[a.outcome];
2102
+ if (Object.keys(a).length !== keys.length || !keys.every((k) => k in a)) return false;
2103
+ return a.outcome === "refused" ? CREATE_READY_SESSION_REFUSALS.includes(a.reason) : typeof a.session_id === "string" && UUID_RE.test(a.session_id);
2104
+ }
2105
+ function sealIdOk(sealId) {
2106
+ if (!UUID_RE.test(sealId)) throw new RangeError(`seal id must be a lowercase uuid: ${sealId}`);
2107
+ return sealId;
2108
+ }
2109
+ function dispatchConversationId(sealId) {
2110
+ return DISPATCH_SESSION_CID_PREFIX + sealIdOk(sealId);
2111
+ }
2112
+ function dispatchSessionName(sealId) {
2113
+ return DISPATCH_SESSION_NAME_PREFIX + sealIdOk(sealId).replace(/-/g, "").slice(0, 12);
2114
+ }
2115
+ var RUNNER_OUTCOMES = ["done", "no_job", "failed", "refused"];
2116
+ var RUNNER_OUTCOME_FIELDS = [
2117
+ "v",
2118
+ "outcome",
2119
+ "session_id",
2120
+ "lease_epoch",
2121
+ "contract_sha256",
2122
+ "contract_version",
2123
+ "merge_strategy",
2124
+ "pr_url",
2125
+ "reason"
2126
+ ];
2127
+ var RUNNER_EXIT_CODES = Object.freeze({
2128
+ done: 0,
2129
+ no_job: 0,
2130
+ failed: 1,
2131
+ usage: 2,
2132
+ refused: 3
2133
+ });
2134
+ var RUNNER_FAILED_REASONS = ["pr_open_failed", "pr_no_changes"];
2135
+ var RUNNER_FAILED_PHASE_RE = /^phase_[0-9]+_[a-z0-9_]+$/;
2136
+ var RUNNER_FAIL_REFS = ["reason"];
2137
+ var RUNNER_FAILED_REASON_PATTERN = `^(${RUNNER_FAILED_REASONS.join("|")}|phase_[0-9]+_[a-z0-9_]+)$`;
2138
+ function isRunnerFailedReason(v) {
2139
+ return typeof v === "string" && (RUNNER_FAILED_REASONS.includes(v) || RUNNER_FAILED_PHASE_RE.test(v));
2140
+ }
2141
+ var POOL_JOB_RETRIES_DEFAULT = 1;
2142
+ var POOL_JOB_RETRIES_MIN = 0;
2143
+ var POOL_JOB_RETRIES_MAX = 5;
2144
+ var POOL_JOB_RETRIES_CONFIG_CAUSE = "pool_job_retries_config";
2145
+ function poolJobRetriesOf(limit) {
2146
+ if (limit === void 0 || limit === null) return POOL_JOB_RETRIES_DEFAULT;
2147
+ return Number.isInteger(limit) && limit >= POOL_JOB_RETRIES_MIN && limit <= POOL_JOB_RETRIES_MAX ? limit : null;
2148
+ }
2149
+ var RUNNER_REFUSED_REASONS = [
2150
+ "bundle_too_large",
2151
+ "bundle_malformed",
2152
+ "bundle_wrong_typ",
2153
+ "bundle_schema",
2154
+ "bundle_unknown_key",
2155
+ "bundle_unusable_key",
2156
+ "bundle_retired_key",
2157
+ "bundle_bad_signature",
2158
+ "bundle_code_digest_mismatch",
2159
+ "bundle_not_yet_valid",
2160
+ "bundle_expired",
2161
+ "bundle_binding_mismatch",
2162
+ "bundle_unavailable",
2163
+ "job_unavailable",
2164
+ "contract_bad_sha",
2165
+ "contract_too_large",
2166
+ "contract_sha_mismatch",
2167
+ "contract_not_a_contract",
2168
+ "contract_mismatch_unrecoverable",
2169
+ "claim_needs_key",
2170
+ "claim_ineligible",
2171
+ "claim_unavailable",
2172
+ "lease_lost",
2173
+ "executor_revoked",
2174
+ "executor_unauthorized",
2175
+ "oidc_bad_issuer",
2176
+ "oidc_repo_not_enrolled",
2177
+ "oidc_ref_not_allowed",
2178
+ "oidc_pool_flag_off",
2179
+ "oidc_unavailable",
2180
+ "api_unavailable"
2181
+ ];
2182
+ var RUNNER_OPEN_MODE_TOLERATED = [
2183
+ "bundle_unavailable",
2184
+ "job_unavailable",
2185
+ "api_unavailable"
2186
+ ];
2187
+ var RUNNER_MERGE_STRATEGIES = ["external", "merge_on_green"];
2188
+ var POOL_CLAIM_FIELDS = ["contract_sha256", "contract_version", "cap"];
2189
+ var HEX64_RE2 = /^[0-9a-f]{64}$/;
2190
+ function poolClaimFieldsOk(body, pool) {
2191
+ if (!pool) return POOL_CLAIM_FIELDS.every((k) => !(k in body));
2192
+ return typeof body.contract_sha256 === "string" && HEX64_RE2.test(body.contract_sha256) && Number.isInteger(body.contract_version) && body.contract_version >= 1 && capOk(body.cap);
2193
+ }
2194
+ var WAIT_REF_KEYS = Object.freeze({
2195
+ ci: ["repo_id", "head_sha"],
2196
+ question: ["approval_id"],
2197
+ budget: ["approval_id"]
2198
+ });
2199
+ var HEX40_RE = /^[0-9a-f]{40}$/;
2200
+ function waitRefOk(reason, ref) {
2201
+ if (!Object.prototype.hasOwnProperty.call(WAIT_REF_KEYS, reason)) return false;
2202
+ if (ref === null || typeof ref !== "object" || Array.isArray(ref)) return false;
2203
+ const r = ref;
2204
+ const want = WAIT_REF_KEYS[reason];
2205
+ if (Object.keys(r).length !== want.length || !want.every((k) => k in r)) return false;
2206
+ if (reason === "ci")
2207
+ return Number.isSafeInteger(r.repo_id) && r.repo_id > 0 && typeof r.head_sha === "string" && HEX40_RE.test(r.head_sha);
2208
+ return typeof r.approval_id === "string" && UUID_RE.test(r.approval_id);
2209
+ }
2210
+ var CLEAR_WAIT_OUTCOMES = ["cleared", "stale_ref", "already_clear"];
2211
+ var READY_SESSIONS_LIMIT_MIN = 1;
2212
+ var READY_SESSIONS_LIMIT_MAX = 50;
2213
+
2037
2214
  // ../shared/durable-contract/src/events.ts
2038
2215
  var EVENT_GROUPS = [
2039
2216
  "lifecycle",
@@ -2337,6 +2514,7 @@ var ENUM = (values) => ({
2337
2514
  });
2338
2515
  var obj = (properties, required = Object.keys(properties), if_then) => if_then ? { type: "object", additionalProperties: false, required, properties, if_then } : { type: "object", additionalProperties: false, required, properties };
2339
2516
  var EMPTY = obj({});
2517
+ var RUNNER_REASON = S({ pattern: RUNNER_FAILED_REASON_PATTERN });
2340
2518
  var APPROVAL_KINDS = [
2341
2519
  "question",
2342
2520
  "ask",
@@ -2347,7 +2525,7 @@ var APPROVAL_KINDS = [
2347
2525
  "grant_renewal",
2348
2526
  "limit_raise"
2349
2527
  ];
2350
- var READY_CAUSES = ["dispatched", "lease_expired", "wait_cleared"];
2528
+ var READY_CAUSES = ["dispatched", "lease_expired", "wait_cleared", "job_failed"];
2351
2529
  var OWNER_MUTATION_SUBJECTS = [
2352
2530
  "unlock",
2353
2531
  "unpause_contained",
@@ -2404,7 +2582,15 @@ var KIND_REFS = {
2404
2582
  }
2405
2583
  ]
2406
2584
  ),
2407
- "session.failed": obj({ reason: ENUM([...FAILED_REASONS]) }),
2585
+ // S1 amendment 59: runner_reason is required with runner_failed and absent otherwise.
2586
+ "session.failed": obj(
2587
+ { reason: ENUM([...FAILED_REASONS]), runner_reason: RUNNER_REASON },
2588
+ ["reason"],
2589
+ [
2590
+ { field: "reason", equals: "runner_failed", require: ["runner_reason"] },
2591
+ { field: "reason", equals: "transcript_lost", forbid: ["runner_reason"] }
2592
+ ]
2593
+ ),
2408
2594
  "session.killed": obj({ reason: ENUM(["owner", "agent_killed"]) }),
2409
2595
  "seat.hello": obj({ spawn_id: UUID }),
2410
2596
  "charter.sealed": obj({
@@ -2477,7 +2663,14 @@ var KIND_REFS = {
2477
2663
  "containment.kill": obj({ action: ENUM(["kill"]) }),
2478
2664
  "never.held": obj({ sh_decision_id: UUID }),
2479
2665
  // ---- Doors amendment (rulings 34.02, 34.06) ----
2480
- "session.ready": obj({ cause: ENUM(READY_CAUSES) }),
2666
+ // S1 amendment 59: a retried job's ready names the runner's reason and the attempt it ends.
2667
+ "session.ready": obj(
2668
+ { cause: ENUM(READY_CAUSES), runner_reason: RUNNER_REASON, attempt: INT(1) },
2669
+ ["cause"],
2670
+ READY_CAUSES.map(
2671
+ (cause) => cause === "job_failed" ? { field: "cause", equals: cause, require: ["runner_reason", "attempt"] } : { field: "cause", equals: cause, forbid: ["runner_reason", "attempt"] }
2672
+ )
2673
+ ),
2481
2674
  "session.waiting": obj(
2482
2675
  {
2483
2676
  wait_reason: ENUM(PARKED_ONLY_WAIT_REASONS),
@@ -3457,7 +3650,8 @@ var M1_COLUMNS = {
3457
3650
  "created_at",
3458
3651
  "updated_at",
3459
3652
  "wait_ref",
3460
- "new_session"
3653
+ "new_session",
3654
+ "job_failures"
3461
3655
  ],
3462
3656
  durable_lease_keys: [
3463
3657
  "key_hash",
@@ -4430,170 +4624,6 @@ function gatewayEventsBodyOk(v) {
4430
4624
  return validateRefs(kind, v.refs ?? {});
4431
4625
  }
4432
4626
 
4433
- // ../shared/durable-contract/src/doors.ts
4434
- var DURABLE_POOL_CLAIM_FLAG = "DURABLE_POOL_CLAIM";
4435
- function poolClaimEnabled(flagValue, orgId) {
4436
- return poolClaimOrgs(flagValue).includes(orgId);
4437
- }
4438
- function isPoolSession(s, flagValue) {
4439
- return s.home_executor_id === null && s.durable_mode === "locked" && poolClaimEnabled(flagValue, s.org_id);
4440
- }
4441
- function poolClaimOrgs(flagValue) {
4442
- if (flagValue === void 0) return [];
4443
- const orgs = flagValue.split(",").map((s) => s.trim());
4444
- if (orgs.some((o) => !ID_RE.test(o))) return [];
4445
- return [...new Set(orgs)];
4446
- }
4447
- var EXECUTOR_SCOPES = ["user", "org"];
4448
- var ORG_SCOPE_EXECUTOR_KINDS = ["container", "fleet"];
4449
- var OIDC_EXCHANGE_REFUSALS = [
4450
- "bad_issuer",
4451
- "repo_not_enrolled",
4452
- "ref_not_allowed",
4453
- "pool_flag_off"
4454
- ];
4455
- var OIDC_EXCHANGE_AUDIENCE = "mnemom-executor";
4456
- var ORG_POLICY_LIMIT_KEYS = ["per_session_budget_usd", "live_lease_cap"];
4457
- var LIVE_LEASE_CAP_MIN = 1;
4458
- var LIVE_LEASE_CAP_MAX = 1e3;
4459
- var ORG_CAP_LOCK_PREFIX = "durable_org_cap:";
4460
- var DEFAULT_LEASE_CAP_ENV = "DURABLE_DEFAULT_LEASE_CAP";
4461
- var capOk = (v) => Number.isInteger(v) && v >= LIVE_LEASE_CAP_MIN && v <= LIVE_LEASE_CAP_MAX;
4462
- function parseDefaultLeaseCap(envValue) {
4463
- if (envValue === void 0 || !/^[1-9][0-9]{0,3}$/.test(envValue)) return null;
4464
- const n = Number(envValue);
4465
- return capOk(n) ? n : null;
4466
- }
4467
- function effectiveLeaseCap(orgLimits, defaultRowLimits, defaultCap) {
4468
- const present = [orgLimits?.live_lease_cap, defaultRowLimits?.live_lease_cap, defaultCap].filter(
4469
- (v) => v !== void 0 && v !== null
4470
- );
4471
- for (const v of present)
4472
- if (!capOk(v)) throw new RangeError(`live lease cap out of range: ${String(v)}`);
4473
- if (present.length === 0)
4474
- throw new RangeError("no live lease cap: DURABLE_DEFAULT_LEASE_CAP is unset");
4475
- return present[0];
4476
- }
4477
- var DISPATCH_SESSION_CID_PREFIX = "dispatch-";
4478
- var DISPATCH_SESSION_NAME_PREFIX = "job-";
4479
- var CREATE_READY_SESSION_REFUSALS = [
4480
- "no_dispatch_agent",
4481
- "seal_not_found",
4482
- "cell_unmapped",
4483
- // 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
4484
- "charter_mismatch"
4485
- ];
4486
- var CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
4487
- var CREATE_READY_SESSION_ANSWERS = Object.freeze({
4488
- created: ["outcome", "session_id"],
4489
- existing: ["outcome", "session_id"],
4490
- refused: ["outcome", "reason"]
4491
- });
4492
- function createReadySessionAnswerOk(v) {
4493
- if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
4494
- const a = v;
4495
- if (typeof a.outcome !== "string" || !Object.prototype.hasOwnProperty.call(CREATE_READY_SESSION_ANSWERS, a.outcome))
4496
- return false;
4497
- const keys = CREATE_READY_SESSION_ANSWERS[a.outcome];
4498
- if (Object.keys(a).length !== keys.length || !keys.every((k) => k in a)) return false;
4499
- return a.outcome === "refused" ? CREATE_READY_SESSION_REFUSALS.includes(a.reason) : typeof a.session_id === "string" && UUID_RE.test(a.session_id);
4500
- }
4501
- function sealIdOk(sealId) {
4502
- if (!UUID_RE.test(sealId)) throw new RangeError(`seal id must be a lowercase uuid: ${sealId}`);
4503
- return sealId;
4504
- }
4505
- function dispatchConversationId(sealId) {
4506
- return DISPATCH_SESSION_CID_PREFIX + sealIdOk(sealId);
4507
- }
4508
- function dispatchSessionName(sealId) {
4509
- return DISPATCH_SESSION_NAME_PREFIX + sealIdOk(sealId).replace(/-/g, "").slice(0, 12);
4510
- }
4511
- var RUNNER_OUTCOMES = ["done", "no_job", "failed", "refused"];
4512
- var RUNNER_OUTCOME_FIELDS = [
4513
- "v",
4514
- "outcome",
4515
- "session_id",
4516
- "lease_epoch",
4517
- "contract_sha256",
4518
- "contract_version",
4519
- "merge_strategy",
4520
- "pr_url",
4521
- "reason"
4522
- ];
4523
- var RUNNER_EXIT_CODES = Object.freeze({
4524
- done: 0,
4525
- no_job: 0,
4526
- failed: 1,
4527
- usage: 2,
4528
- refused: 3
4529
- });
4530
- var RUNNER_FAILED_REASONS = ["pr_open_failed"];
4531
- var RUNNER_FAILED_PHASE_RE = /^phase_[0-9]+_[a-z0-9_]+$/;
4532
- var RUNNER_REFUSED_REASONS = [
4533
- "bundle_too_large",
4534
- "bundle_malformed",
4535
- "bundle_wrong_typ",
4536
- "bundle_schema",
4537
- "bundle_unknown_key",
4538
- "bundle_unusable_key",
4539
- "bundle_retired_key",
4540
- "bundle_bad_signature",
4541
- "bundle_code_digest_mismatch",
4542
- "bundle_not_yet_valid",
4543
- "bundle_expired",
4544
- "bundle_binding_mismatch",
4545
- "bundle_unavailable",
4546
- "job_unavailable",
4547
- "contract_bad_sha",
4548
- "contract_too_large",
4549
- "contract_sha_mismatch",
4550
- "contract_not_a_contract",
4551
- "contract_mismatch_unrecoverable",
4552
- "claim_needs_key",
4553
- "claim_ineligible",
4554
- "claim_unavailable",
4555
- "lease_lost",
4556
- "executor_revoked",
4557
- "executor_unauthorized",
4558
- "oidc_bad_issuer",
4559
- "oidc_repo_not_enrolled",
4560
- "oidc_ref_not_allowed",
4561
- "oidc_pool_flag_off",
4562
- "oidc_unavailable",
4563
- "api_unavailable"
4564
- ];
4565
- var RUNNER_OPEN_MODE_TOLERATED = [
4566
- "bundle_unavailable",
4567
- "job_unavailable",
4568
- "api_unavailable"
4569
- ];
4570
- var RUNNER_MERGE_STRATEGIES = ["external", "merge_on_green"];
4571
- var POOL_CLAIM_FIELDS = ["contract_sha256", "contract_version", "cap"];
4572
- var HEX64_RE2 = /^[0-9a-f]{64}$/;
4573
- function poolClaimFieldsOk(body, pool) {
4574
- if (!pool) return POOL_CLAIM_FIELDS.every((k) => !(k in body));
4575
- return typeof body.contract_sha256 === "string" && HEX64_RE2.test(body.contract_sha256) && Number.isInteger(body.contract_version) && body.contract_version >= 1 && capOk(body.cap);
4576
- }
4577
- var WAIT_REF_KEYS = Object.freeze({
4578
- ci: ["repo_id", "head_sha"],
4579
- question: ["approval_id"],
4580
- budget: ["approval_id"]
4581
- });
4582
- var HEX40_RE = /^[0-9a-f]{40}$/;
4583
- function waitRefOk(reason, ref) {
4584
- if (!Object.prototype.hasOwnProperty.call(WAIT_REF_KEYS, reason)) return false;
4585
- if (ref === null || typeof ref !== "object" || Array.isArray(ref)) return false;
4586
- const r = ref;
4587
- const want = WAIT_REF_KEYS[reason];
4588
- if (Object.keys(r).length !== want.length || !want.every((k) => k in r)) return false;
4589
- if (reason === "ci")
4590
- return Number.isSafeInteger(r.repo_id) && r.repo_id > 0 && typeof r.head_sha === "string" && HEX40_RE.test(r.head_sha);
4591
- return typeof r.approval_id === "string" && UUID_RE.test(r.approval_id);
4592
- }
4593
- var CLEAR_WAIT_OUTCOMES = ["cleared", "stale_ref", "already_clear"];
4594
- var READY_SESSIONS_LIMIT_MIN = 1;
4595
- var READY_SESSIONS_LIMIT_MAX = 50;
4596
-
4597
4627
  // ../shared/durable-contract/src/journal-approvals.ts
4598
4628
  var JOURNAL_APPROVAL_KINDS = ["approval.resolved", "approval.consumed"];
4599
4629
  var idOf = (e2) => e2.refs !== null && typeof e2.refs.approval_id === "string" ? e2.refs.approval_id : null;
@@ -5584,6 +5614,10 @@ export {
5584
5614
  PLUGIN_MANIFEST_SCHEMA,
5585
5615
  PLUGIN_TOKEN,
5586
5616
  POOL_CLAIM_FIELDS,
5617
+ POOL_JOB_RETRIES_CONFIG_CAUSE,
5618
+ POOL_JOB_RETRIES_DEFAULT,
5619
+ POOL_JOB_RETRIES_MAX,
5620
+ POOL_JOB_RETRIES_MIN,
5587
5621
  POSTURES,
5588
5622
  PRE_M1_LAUNCHER_SUBVERBS,
5589
5623
  PRINCIPAL_REF_VECTOR,
@@ -5620,6 +5654,8 @@ export {
5620
5654
  RUNNER_EXIT_CODES,
5621
5655
  RUNNER_FAILED_PHASE_RE,
5622
5656
  RUNNER_FAILED_REASONS,
5657
+ RUNNER_FAILED_REASON_PATTERN,
5658
+ RUNNER_FAIL_REFS,
5623
5659
  RUNNER_MERGE_STRATEGIES,
5624
5660
  RUNNER_OPEN_MODE_TOLERATED,
5625
5661
  RUNNER_OUTCOMES,
@@ -5794,6 +5830,7 @@ export {
5794
5830
  isLoopbackUrl,
5795
5831
  isPoolSession,
5796
5832
  isRuled,
5833
+ isRunnerFailedReason,
5797
5834
  isSessionState,
5798
5835
  isTerminalState,
5799
5836
  isTestAllowlisted,
@@ -5831,6 +5868,7 @@ export {
5831
5868
  poolClaimEnabled,
5832
5869
  poolClaimFieldsOk,
5833
5870
  poolClaimOrgs,
5871
+ poolJobRetriesOf,
5834
5872
  principalRef,
5835
5873
  protectionRefusals,
5836
5874
  proxyLockPath,
@@ -87,8 +87,33 @@ export declare const RUNNER_EXIT_CODES: Readonly<{
87
87
  readonly refused: 3;
88
88
  }>;
89
89
  /** failed reasons besides the phase pattern RUNNER_FAILED_PHASE_RE. */
90
- export declare const RUNNER_FAILED_REASONS: readonly ["pr_open_failed"];
90
+ export declare const RUNNER_FAILED_REASONS: readonly ["pr_open_failed", "pr_no_changes"];
91
91
  export declare const RUNNER_FAILED_PHASE_RE: RegExp;
92
+ /**
93
+ * S1 amendment 59 (signed by Wassim 2026-10-31): a failed pool job is retried, then failed with the
94
+ * runner's reason. exec_fail {reason: "runner_failed"} carries exactly `refs: {reason}`, where
95
+ * refs.reason passes isRunnerFailedReason; anything else is 400 durable_reason_invalid
96
+ * {field: "refs.reason"} with nothing written. `pr_no_changes` is found before any push (no commit
97
+ * ahead of the base) and is retried like any other failure.
98
+ */
99
+ export declare const RUNNER_FAIL_REFS: readonly ["reason"];
100
+ /** RUNNER_FAILED_REASONS and RUNNER_FAILED_PHASE_RE as one anchored pattern (the refs schemas'). */
101
+ export declare const RUNNER_FAILED_REASON_PATTERN: string;
102
+ /** 59 (2): the single check the CLI, the api and E4 share for a runner's failed reason. */
103
+ export declare function isRunnerFailedReason(v: unknown): boolean;
104
+ /**
105
+ * 59 (3): how many times a failed pool job goes back to ready before it is failed. Configured per
106
+ * org as organizations.limits.pool_job_retries (operating policy, never the charter); absent means
107
+ * the default, and a present value outside MIN..MAX fails closed (503 durable_rpc_unavailable
108
+ * {cause: "pool_job_retries_config"}, nothing written), as 34.05.1 (3) does for the lease cap. A
109
+ * lease lapse never spends a retry: it stays resume_failures / respawn_failed.
110
+ */
111
+ export declare const POOL_JOB_RETRIES_DEFAULT = 1;
112
+ export declare const POOL_JOB_RETRIES_MIN = 0;
113
+ export declare const POOL_JOB_RETRIES_MAX = 5;
114
+ export declare const POOL_JOB_RETRIES_CONFIG_CAUSE = "pool_job_retries_config";
115
+ /** The org's retry count from its limits.pool_job_retries: the default when absent, null when invalid. */
116
+ export declare function poolJobRetriesOf(limit: unknown): number | null;
92
117
  /** The closed refused list (34.12 (8)). */
93
118
  export declare const RUNNER_REFUSED_REASONS: readonly ["bundle_too_large", "bundle_malformed", "bundle_wrong_typ", "bundle_schema", "bundle_unknown_key", "bundle_unusable_key", "bundle_retired_key", "bundle_bad_signature", "bundle_code_digest_mismatch", "bundle_not_yet_valid", "bundle_expired", "bundle_binding_mismatch", "bundle_unavailable", "job_unavailable", "contract_bad_sha", "contract_too_large", "contract_sha_mismatch", "contract_not_a_contract", "contract_mismatch_unrecoverable", "claim_needs_key", "claim_ineligible", "claim_unavailable", "lease_lost", "executor_revoked", "executor_unauthorized", "oidc_bad_issuer", "oidc_repo_not_enrolled", "oidc_ref_not_allowed", "oidc_pool_flag_off", "oidc_unavailable", "api_unavailable"];
94
119
  /** Under --fail-mode open, only these may be ridden through, and only once a verified bundle is held (34.12 (2)). */
@@ -154,8 +154,44 @@ export const RUNNER_EXIT_CODES = Object.freeze({
154
154
  refused: 3,
155
155
  });
156
156
  /** failed reasons besides the phase pattern RUNNER_FAILED_PHASE_RE. */
157
- export const RUNNER_FAILED_REASONS = ["pr_open_failed"];
157
+ export const RUNNER_FAILED_REASONS = ["pr_open_failed", "pr_no_changes"];
158
158
  export const RUNNER_FAILED_PHASE_RE = /^phase_[0-9]+_[a-z0-9_]+$/;
159
+ /**
160
+ * S1 amendment 59 (signed by Wassim 2026-10-31): a failed pool job is retried, then failed with the
161
+ * runner's reason. exec_fail {reason: "runner_failed"} carries exactly `refs: {reason}`, where
162
+ * refs.reason passes isRunnerFailedReason; anything else is 400 durable_reason_invalid
163
+ * {field: "refs.reason"} with nothing written. `pr_no_changes` is found before any push (no commit
164
+ * ahead of the base) and is retried like any other failure.
165
+ */
166
+ export const RUNNER_FAIL_REFS = ["reason"];
167
+ /** RUNNER_FAILED_REASONS and RUNNER_FAILED_PHASE_RE as one anchored pattern (the refs schemas'). */
168
+ export const RUNNER_FAILED_REASON_PATTERN = `^(${RUNNER_FAILED_REASONS.join("|")}|phase_[0-9]+_[a-z0-9_]+)$`;
169
+ /** 59 (2): the single check the CLI, the api and E4 share for a runner's failed reason. */
170
+ export function isRunnerFailedReason(v) {
171
+ return (typeof v === "string" &&
172
+ (RUNNER_FAILED_REASONS.includes(v) || RUNNER_FAILED_PHASE_RE.test(v)));
173
+ }
174
+ /**
175
+ * 59 (3): how many times a failed pool job goes back to ready before it is failed. Configured per
176
+ * org as organizations.limits.pool_job_retries (operating policy, never the charter); absent means
177
+ * the default, and a present value outside MIN..MAX fails closed (503 durable_rpc_unavailable
178
+ * {cause: "pool_job_retries_config"}, nothing written), as 34.05.1 (3) does for the lease cap. A
179
+ * lease lapse never spends a retry: it stays resume_failures / respawn_failed.
180
+ */
181
+ export const POOL_JOB_RETRIES_DEFAULT = 1;
182
+ export const POOL_JOB_RETRIES_MIN = 0;
183
+ export const POOL_JOB_RETRIES_MAX = 5;
184
+ export const POOL_JOB_RETRIES_CONFIG_CAUSE = "pool_job_retries_config";
185
+ /** The org's retry count from its limits.pool_job_retries: the default when absent, null when invalid. */
186
+ export function poolJobRetriesOf(limit) {
187
+ if (limit === undefined || limit === null)
188
+ return POOL_JOB_RETRIES_DEFAULT;
189
+ return Number.isInteger(limit) &&
190
+ limit >= POOL_JOB_RETRIES_MIN &&
191
+ limit <= POOL_JOB_RETRIES_MAX
192
+ ? limit
193
+ : null;
194
+ }
159
195
  /** The closed refused list (34.12 (8)). */
160
196
  export const RUNNER_REFUSED_REASONS = [
161
197
  "bundle_too_large",
@@ -90,7 +90,7 @@ export interface RefsSchema {
90
90
  /** durable_approvals.kind values, as the approval.* refs enums carry them. */
91
91
  export declare const APPROVAL_KINDS: readonly ["question", "ask", "tool_confirmation", "intent_rerun", "card_drift", "owner_mutation", "grant_renewal", "limit_raise"];
92
92
  /** session.ready refs.cause: how a session entered ready (ruling 34.02 (3)). */
93
- export declare const READY_CAUSES: readonly ["dispatched", "lease_expired", "wait_cleared"];
93
+ export declare const READY_CAUSES: readonly ["dispatched", "lease_expired", "wait_cleared", "job_failed"];
94
94
  /** refs_schema for every M1 kind; kinds without a specific schema take EMPTY (refs must be {}). */
95
95
  export declare function refsSchemaOf(kind: DurableEventKind): RefsSchema;
96
96
  /** One refs property against its schema (pure; exported for its direct test). An integer with no minimum accepts any safe integer. */
@@ -25,6 +25,7 @@ import { canonicalJson, hmacSha256Hex, sha256Hex, HEX64_RE } from "./canonical.j
25
25
  import { DETACHED_REASONS, PAUSED_REASONS, SUSPENDED_REASONS, FAILED_REASONS, PARKED_ONLY_WAIT_REASONS, } from "./states.js";
26
26
  import { ATTESTATION_CHECKS_M1, CWD_CHECK_CAUSES } from "./codes.js";
27
27
  import { EVENT_DETAIL_MAX_CHARS } from "./constants.js";
28
+ import { RUNNER_FAILED_REASON_PATTERN } from "./doors.js";
28
29
  export const EVENT_GROUPS = [
29
30
  "lifecycle",
30
31
  "charter",
@@ -352,6 +353,8 @@ const obj = (properties, required = Object.keys(properties), if_then) => if_then
352
353
  ? { type: "object", additionalProperties: false, required, properties, if_then }
353
354
  : { type: "object", additionalProperties: false, required, properties };
354
355
  const EMPTY = obj({});
356
+ /** A runner's failed reason (S1 amendment 59): RUNNER_FAILED_REASONS or the phase pattern. */
357
+ const RUNNER_REASON = S({ pattern: RUNNER_FAILED_REASON_PATTERN });
355
358
  /** durable_approvals.kind values, as the approval.* refs enums carry them. */
356
359
  export const APPROVAL_KINDS = [
357
360
  "question",
@@ -364,7 +367,7 @@ export const APPROVAL_KINDS = [
364
367
  "limit_raise",
365
368
  ];
366
369
  /** session.ready refs.cause: how a session entered ready (ruling 34.02 (3)). */
367
- export const READY_CAUSES = ["dispatched", "lease_expired", "wait_cleared"];
370
+ export const READY_CAUSES = ["dispatched", "lease_expired", "wait_cleared", "job_failed"];
368
371
  // question joins the M1 four (ruling 34.06.1.1): its wait parks on an owner_mutation approval.
369
372
  const OWNER_MUTATION_SUBJECTS = [
370
373
  "unlock",
@@ -415,7 +418,11 @@ const KIND_REFS = {
415
418
  },
416
419
  },
417
420
  ]),
418
- "session.failed": obj({ reason: ENUM([...FAILED_REASONS]) }),
421
+ // S1 amendment 59: runner_reason is required with runner_failed and absent otherwise.
422
+ "session.failed": obj({ reason: ENUM([...FAILED_REASONS]), runner_reason: RUNNER_REASON }, ["reason"], [
423
+ { field: "reason", equals: "runner_failed", require: ["runner_reason"] },
424
+ { field: "reason", equals: "transcript_lost", forbid: ["runner_reason"] },
425
+ ]),
419
426
  "session.killed": obj({ reason: ENUM(["owner", "agent_killed"]) }),
420
427
  "seat.hello": obj({ spawn_id: UUID }),
421
428
  "charter.sealed": obj({
@@ -478,7 +485,10 @@ const KIND_REFS = {
478
485
  "containment.kill": obj({ action: ENUM(["kill"]) }),
479
486
  "never.held": obj({ sh_decision_id: UUID }),
480
487
  // ---- Doors amendment (rulings 34.02, 34.06) ----
481
- "session.ready": obj({ cause: ENUM(READY_CAUSES) }),
488
+ // S1 amendment 59: a retried job's ready names the runner's reason and the attempt it ends.
489
+ "session.ready": obj({ cause: ENUM(READY_CAUSES), runner_reason: RUNNER_REASON, attempt: INT(1) }, ["cause"], READY_CAUSES.map((cause) => cause === "job_failed"
490
+ ? { field: "cause", equals: cause, require: ["runner_reason", "attempt"] }
491
+ : { field: "cause", equals: cause, forbid: ["runner_reason", "attempt"] })),
482
492
  "session.waiting": obj({
483
493
  wait_reason: ENUM(PARKED_ONLY_WAIT_REASONS),
484
494
  head_sha: S({ pattern: HEX40_PATTERN, maxLength: 40 }),
@@ -709,7 +709,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
709
709
  }>;
710
710
  }>;
711
711
  GATEWAY_EVENTS_BODY_KEYS: readonly ["session_id", "kind", "epoch", "idempotency_key", "refs"];
712
- READY_CAUSES: readonly ["dispatched", "lease_expired", "wait_cleared"];
712
+ READY_CAUSES: readonly ["dispatched", "lease_expired", "wait_cleared", "job_failed"];
713
713
  WAIT_REASONS: readonly ["approval", "moving", "mnemom_unavailable", "ci", "question", "budget"];
714
714
  PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget"];
715
715
  WAIT_REF_KEYS: Readonly<{
@@ -740,7 +740,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
740
740
  readonly usage: 2;
741
741
  readonly refused: 3;
742
742
  }>;
743
- RUNNER_FAILED_REASONS: readonly ["pr_open_failed"];
743
+ RUNNER_FAILED_REASONS: readonly ["pr_open_failed", "pr_no_changes"];
744
744
  RUNNER_REFUSED_REASONS: readonly ["bundle_too_large", "bundle_malformed", "bundle_wrong_typ", "bundle_schema", "bundle_unknown_key", "bundle_unusable_key", "bundle_retired_key", "bundle_bad_signature", "bundle_code_digest_mismatch", "bundle_not_yet_valid", "bundle_expired", "bundle_binding_mismatch", "bundle_unavailable", "job_unavailable", "contract_bad_sha", "contract_too_large", "contract_sha_mismatch", "contract_not_a_contract", "contract_mismatch_unrecoverable", "claim_needs_key", "claim_ineligible", "claim_unavailable", "lease_lost", "executor_revoked", "executor_unauthorized", "oidc_bad_issuer", "oidc_repo_not_enrolled", "oidc_ref_not_allowed", "oidc_pool_flag_off", "oidc_unavailable", "api_unavailable"];
745
745
  RUNNER_OPEN_MODE_TOLERATED: readonly ["bundle_unavailable", "job_unavailable", "api_unavailable"];
746
746
  RUNNER_MERGE_STRATEGIES: readonly ["external", "merge_on_green"];
@@ -22,6 +22,11 @@
22
22
  * transaction with the lock); a plain lock never sets it. The executor learns it only from the
23
23
  * claim success answer (`new_session: true`, while no seat hello was ever accepted for the
24
24
  * session); EXEC_SESSIONS_RESPONSE item keys are unchanged.
25
+ *
26
+ * Amendment 59 (signed by Wassim 2026-10-31), appended to its table:
27
+ * - agent_sessions.job_failures (INTEGER NOT NULL DEFAULT 0, CHECK >= 0): the pool job's accepted
28
+ * runner_failed count, never reset. Separate from resume_failures, so a lease lapse never spends
29
+ * a job retry and a job retry never counts towards respawn_failed.
25
30
  */
26
31
  export declare const M1_TABLES: readonly ["agent_sessions", "durable_lease_keys", "agent_session_charters", "agent_session_events", "durable_event_kinds", "agent_session_chains", "agent_executors", "agent_durable_profiles", "durable_cell_state", "durable_org_policy", "durable_user_policy", "durable_approvals", "durable_approval_attestations"];
27
32
  export type M1Table = (typeof M1_TABLES)[number];
@@ -23,6 +23,11 @@
23
23
  * transaction with the lock); a plain lock never sets it. The executor learns it only from the
24
24
  * claim success answer (`new_session: true`, while no seat hello was ever accepted for the
25
25
  * session); EXEC_SESSIONS_RESPONSE item keys are unchanged.
26
+ *
27
+ * Amendment 59 (signed by Wassim 2026-10-31), appended to its table:
28
+ * - agent_sessions.job_failures (INTEGER NOT NULL DEFAULT 0, CHECK >= 0): the pool job's accepted
29
+ * runner_failed count, never reset. Separate from resume_failures, so a lease lapse never spends
30
+ * a job retry and a job retry never counts towards respawn_failed.
26
31
  */
27
32
  import { canonicalJson, sha256Hex, UUID_RE } from "./canonical.js";
28
33
  import { CANONICAL_CELL_IDS } from "./cell-id.js";
@@ -84,6 +89,7 @@ export const M1_COLUMNS = {
84
89
  "updated_at",
85
90
  "wait_ref",
86
91
  "new_session",
92
+ "job_failures",
87
93
  ],
88
94
  durable_lease_keys: [
89
95
  "key_hash",
@@ -12,7 +12,8 @@ export declare const DETACHED_REASONS: readonly ["lease_expired", "hello_missed"
12
12
  export declare const SUSPENDED_REASONS: readonly ["budget_reached", "enforce_refused", "charter_breach", "account_balance", "identity_mismatch", "grant_expired", "card_drift", "attestation_failed", "owner_departed", "respawn_failed", "approval_expired", "approval_denied"];
13
13
  /** breakglass is P0a-B; it stays in the enum so the CHECK never needs widening. */
14
14
  export declare const PAUSED_REASONS: readonly ["owner", "agent_contained", "breakglass"];
15
- export declare const FAILED_REASONS: readonly ["transcript_lost"];
15
+ /** S1 amendment 59: runner_failed is a pool job's terminal failure, after its retries (doors.ts). */
16
+ export declare const FAILED_REASONS: readonly ["transcript_lost", "runner_failed"];
16
17
  export declare const KILLED_REASONS: readonly ["agent_killed"];
17
18
  export type DetachedReason = (typeof DETACHED_REASONS)[number];
18
19
  export type SuspendedReason = (typeof SUSPENDED_REASONS)[number];