@mnemom/mnemom 0.17.39-next.0 → 0.17.39-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent-run.js +2 -0
- package/dist/lib/runner/oidc.d.ts +6 -1
- package/dist/lib/runner/oidc.js +8 -2
- package/dist/vendor/durable-contract/index.js +147 -12
- package/dist/vendor/shared/durable-contract/codes.d.ts +13 -0
- package/dist/vendor/shared/durable-contract/codes.js +10 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +68 -4
- package/dist/vendor/shared/durable-contract/doors.js +110 -5
- package/dist/vendor/shared/durable-contract/events.d.ts +2 -1
- package/dist/vendor/shared/durable-contract/events.js +10 -1
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +23 -5
- package/dist/vendor/shared/durable-contract/freeze-record.js +23 -3
- package/dist/vendor/shared/durable-contract/routes.d.ts +23 -1
- package/dist/vendor/shared/durable-contract/routes.js +34 -1
- package/dist/vendor/shared/durable-contract/schema.d.ts +3 -3
- package/dist/vendor/shared/durable-contract/schema.js +7 -4
- package/dist/vendor/shared/durable-contract/states.d.ts +16 -3
- package/dist/vendor/shared/durable-contract/states.js +20 -1
- package/package.json +1 -1
|
@@ -72,6 +72,8 @@ export async function agentRunCommand(flags, deps) {
|
|
|
72
72
|
apiUrl: deps.apiUrl,
|
|
73
73
|
requestUrl: plan.credential.requestUrl,
|
|
74
74
|
requestToken: plan.credential.requestToken,
|
|
75
|
+
// Amendment 66: set by the action from the wake-up's adopt-ticket input; empty sends none.
|
|
76
|
+
adoptTicket: deps.env.MNEMOM_ADOPT_TICKET?.trim() || null,
|
|
75
77
|
});
|
|
76
78
|
if (!ex.ok) {
|
|
77
79
|
// No job is held yet: fail closed whatever --fail-mode says (34.12 (6)).
|
|
@@ -30,10 +30,15 @@ export declare function actionsIdToken(fetch: FetchFn, requestUrl: string, reque
|
|
|
30
30
|
ok: false;
|
|
31
31
|
detail: string;
|
|
32
32
|
}>;
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Fetch the ID token and exchange it for a one-lease executor key. `adoptTicket` (Amendment 66) is
|
|
35
|
+
* the ticket the api's wake-up passed this run as the adopt-ticket input; it rides in the body
|
|
36
|
+
* beside the token, and like the token it never reaches a log line or an outcome.
|
|
37
|
+
*/
|
|
34
38
|
export declare function exchangeOidc(opts: {
|
|
35
39
|
fetch: FetchFn;
|
|
36
40
|
apiUrl: string;
|
|
37
41
|
requestUrl: string;
|
|
38
42
|
requestToken: string;
|
|
43
|
+
adoptTicket?: string | null;
|
|
39
44
|
}): Promise<OidcExchange>;
|
package/dist/lib/runner/oidc.js
CHANGED
|
@@ -54,7 +54,11 @@ export async function actionsIdToken(fetch, requestUrl, requestToken) {
|
|
|
54
54
|
return { ok: false, detail: `ID token request failed: ${e.message}` };
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Fetch the ID token and exchange it for a one-lease executor key. `adoptTicket` (Amendment 66) is
|
|
59
|
+
* the ticket the api's wake-up passed this run as the adopt-ticket input; it rides in the body
|
|
60
|
+
* beside the token, and like the token it never reaches a log line or an outcome.
|
|
61
|
+
*/
|
|
58
62
|
export async function exchangeOidc(opts) {
|
|
59
63
|
const id = await actionsIdToken(opts.fetch, opts.requestUrl, opts.requestToken);
|
|
60
64
|
if (!id.ok)
|
|
@@ -65,7 +69,9 @@ export async function exchangeOidc(opts) {
|
|
|
65
69
|
res = await opts.fetch(`${base}${routePath("exec_oidc_exchange")}`, {
|
|
66
70
|
method: "POST",
|
|
67
71
|
headers: { accept: "application/json", "content-type": "application/json" },
|
|
68
|
-
body: JSON.stringify(
|
|
72
|
+
body: JSON.stringify(opts.adoptTicket
|
|
73
|
+
? { oidc_token: id.token, adopt_ticket: opts.adoptTicket }
|
|
74
|
+
: { oidc_token: id.token }),
|
|
69
75
|
});
|
|
70
76
|
}
|
|
71
77
|
catch (e) {
|
|
@@ -238,12 +238,20 @@ var WAIT_REASONS = [
|
|
|
238
238
|
"mnemom_unavailable",
|
|
239
239
|
"ci",
|
|
240
240
|
"question",
|
|
241
|
-
"budget"
|
|
241
|
+
"budget",
|
|
242
|
+
"awaiting_issue_run",
|
|
243
|
+
"repo_arm_unreadable"
|
|
242
244
|
];
|
|
243
245
|
var PARKED_ONLY_WAIT_REASONS = [
|
|
244
246
|
"ci",
|
|
245
247
|
"question",
|
|
246
|
-
"budget"
|
|
248
|
+
"budget",
|
|
249
|
+
"awaiting_issue_run",
|
|
250
|
+
"repo_arm_unreadable"
|
|
251
|
+
];
|
|
252
|
+
var DISPATCH_PARK_WAIT_REASONS = [
|
|
253
|
+
"awaiting_issue_run",
|
|
254
|
+
"repo_arm_unreadable"
|
|
247
255
|
];
|
|
248
256
|
function isSessionState(v) {
|
|
249
257
|
return typeof v === "string" && SESSION_STATES.includes(v);
|
|
@@ -1066,6 +1074,7 @@ var CLAIM_UNAVAILABLE_CAUSES = [
|
|
|
1066
1074
|
"lease_cap_config"
|
|
1067
1075
|
];
|
|
1068
1076
|
var SEAL_REPO_MISMATCH_CAUSES = ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
|
|
1077
|
+
var ADOPT_REFUSED_CAUSES = ["no_parked_seal", "not_parked"];
|
|
1069
1078
|
var UNSUPPORTED_FEATURES = [
|
|
1070
1079
|
"tool_classes",
|
|
1071
1080
|
"launch_spec_change",
|
|
@@ -1179,6 +1188,9 @@ var DURABLE_CODES = {
|
|
|
1179
1188
|
// durable_claim_ineligible reason: the session is claimable, only not by this executor, and a
|
|
1180
1189
|
// runner never skips past it to another session. cause is a SEAL_REPO_MISMATCH_CAUSES value.
|
|
1181
1190
|
seal_repo_mismatch: { status: 403, surfaces: ["api"], fields: ["cause"] },
|
|
1191
|
+
// S1 amendment 64 (mnemom-test#1336): exec_adopt found nothing it may unpark for this run. Nothing
|
|
1192
|
+
// is written. cause is an ADOPT_REFUSED_CAUSES value.
|
|
1193
|
+
adopt_refused: { status: 409, surfaces: ["api"], fields: ["cause"] },
|
|
1182
1194
|
// ---- api journal (C10-R0 (i), (j), (k)) ----
|
|
1183
1195
|
/** Lost CAS race on (session_id, seq). */
|
|
1184
1196
|
durable_append_conflict: { status: 409, surfaces: ["api"], fields: ["tip_seq"] },
|
|
@@ -2372,9 +2384,47 @@ var OIDC_EXCHANGE_REFUSALS = [
|
|
|
2372
2384
|
"bad_issuer",
|
|
2373
2385
|
"repo_not_enrolled",
|
|
2374
2386
|
"ref_not_allowed",
|
|
2375
|
-
"pool_flag_off"
|
|
2387
|
+
"pool_flag_off",
|
|
2388
|
+
"adopt_ticket_invalid",
|
|
2389
|
+
"token_replayed",
|
|
2390
|
+
"run_already_exchanged"
|
|
2376
2391
|
];
|
|
2392
|
+
var OIDC_EXCHANGE_RUN_CLAIMS = ["jti", "run_id", "run_attempt"];
|
|
2393
|
+
var OIDC_JTI_MAX = 200;
|
|
2394
|
+
var OIDC_RUN_NUMBER_RE = /^[1-9][0-9]{0,15}$/;
|
|
2395
|
+
var OIDC_EXCHANGE_RETENTION_DAYS = 35;
|
|
2396
|
+
function oidcRunClaimsOk(claims) {
|
|
2397
|
+
const { jti, run_id, run_attempt } = claims;
|
|
2398
|
+
return typeof jti === "string" && jti.length >= 1 && jti.length <= OIDC_JTI_MAX && typeof run_id === "string" && OIDC_RUN_NUMBER_RE.test(run_id) && typeof run_attempt === "string" && OIDC_RUN_NUMBER_RE.test(run_attempt);
|
|
2399
|
+
}
|
|
2377
2400
|
var OIDC_EXCHANGE_AUDIENCE = "mnemom-executor";
|
|
2401
|
+
var ADOPT_TICKET_INPUT = "adopt-ticket";
|
|
2402
|
+
var WAKEUP_SESSION_INPUT = "session-id";
|
|
2403
|
+
var ADOPT_TICKET_RE = /^mnm_at_[A-Za-z0-9_-]{43}$/;
|
|
2404
|
+
var ADOPT_TICKET_TTL_S = 900;
|
|
2405
|
+
var OIDC_EXCHANGE_BODY_KEYS = ["oidc_token", "adopt_ticket"];
|
|
2406
|
+
var OIDC_TOKEN_MAX = 8192;
|
|
2407
|
+
var OIDC_WORKFLOW_ENV = "DISPATCH_WAKEUP_WORKFLOW";
|
|
2408
|
+
var OIDC_WORKFLOW_FILE_RE = /^[A-Za-z0-9_.-]{1,100}\.ya?ml$/;
|
|
2409
|
+
function adoptTicketOk(t) {
|
|
2410
|
+
return typeof t === "string" && ADOPT_TICKET_RE.test(t);
|
|
2411
|
+
}
|
|
2412
|
+
function oidcExchangeBodyOk(v) {
|
|
2413
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2414
|
+
const o = v;
|
|
2415
|
+
const keys = Object.keys(o);
|
|
2416
|
+
if (keys.some((k) => !OIDC_EXCHANGE_BODY_KEYS.includes(k))) return false;
|
|
2417
|
+
if (typeof o.oidc_token !== "string" || o.oidc_token === "" || o.oidc_token.length > OIDC_TOKEN_MAX)
|
|
2418
|
+
return false;
|
|
2419
|
+
return !("adopt_ticket" in o) || typeof o.adopt_ticket === "string";
|
|
2420
|
+
}
|
|
2421
|
+
function oidcPinnedWorkflow(envValue) {
|
|
2422
|
+
const wf = envValue?.trim() ?? "";
|
|
2423
|
+
return OIDC_WORKFLOW_FILE_RE.test(wf) ? wf : null;
|
|
2424
|
+
}
|
|
2425
|
+
function jobWorkflowRefOk(jobWorkflowRef, repository, workflowFile, defaultBranch) {
|
|
2426
|
+
return typeof jobWorkflowRef === "string" && jobWorkflowRef === `${repository}/.github/workflows/${workflowFile}@refs/heads/${defaultBranch}`;
|
|
2427
|
+
}
|
|
2378
2428
|
var ORG_POLICY_LIMIT_KEYS = ["per_session_budget_usd", "live_lease_cap"];
|
|
2379
2429
|
var LIVE_LEASE_CAP_MIN = 1;
|
|
2380
2430
|
var LIVE_LEASE_CAP_MAX = 1e3;
|
|
@@ -2408,14 +2458,15 @@ var CREATE_READY_SESSION_REFUSALS = [
|
|
|
2408
2458
|
// 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
|
|
2409
2459
|
"charter_mismatch"
|
|
2410
2460
|
];
|
|
2411
|
-
var CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
|
|
2461
|
+
var CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused", "parked"];
|
|
2412
2462
|
function isUserCreator(createdBy) {
|
|
2413
2463
|
return typeof createdBy === "string" && createdBy !== "" && !createdBy.includes(":");
|
|
2414
2464
|
}
|
|
2415
2465
|
var CREATE_READY_SESSION_ANSWERS = Object.freeze({
|
|
2416
2466
|
created: ["outcome", "session_id"],
|
|
2417
2467
|
existing: ["outcome", "session_id"],
|
|
2418
|
-
refused: ["outcome", "reason"]
|
|
2468
|
+
refused: ["outcome", "reason"],
|
|
2469
|
+
parked: ["outcome", "session_id", "wait_reason"]
|
|
2419
2470
|
});
|
|
2420
2471
|
function createReadySessionAnswerOk(v) {
|
|
2421
2472
|
if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
|
|
@@ -2424,7 +2475,11 @@ function createReadySessionAnswerOk(v) {
|
|
|
2424
2475
|
return false;
|
|
2425
2476
|
const keys = CREATE_READY_SESSION_ANSWERS[a.outcome];
|
|
2426
2477
|
if (Object.keys(a).length !== keys.length || !keys.every((k) => k in a)) return false;
|
|
2427
|
-
|
|
2478
|
+
if (a.outcome === "refused")
|
|
2479
|
+
return CREATE_READY_SESSION_REFUSALS.includes(a.reason);
|
|
2480
|
+
if (a.outcome === "parked" && !DISPATCH_PARK_WAIT_REASONS.includes(a.wait_reason))
|
|
2481
|
+
return false;
|
|
2482
|
+
return typeof a.session_id === "string" && UUID_RE.test(a.session_id);
|
|
2428
2483
|
}
|
|
2429
2484
|
function sealIdOk(sealId) {
|
|
2430
2485
|
if (!UUID_RE.test(sealId)) throw new RangeError(`seal id must be a lowercase uuid: ${sealId}`);
|
|
@@ -2501,7 +2556,11 @@ var RUNNER_REFUSED_REASONS = [
|
|
|
2501
2556
|
"oidc_ref_not_allowed",
|
|
2502
2557
|
"oidc_pool_flag_off",
|
|
2503
2558
|
"oidc_unavailable",
|
|
2504
|
-
"api_unavailable"
|
|
2559
|
+
"api_unavailable",
|
|
2560
|
+
"oidc_adopt_ticket_invalid",
|
|
2561
|
+
// amendment 65 (mnemom-test#1331): the two single-use exchange refusals, appended
|
|
2562
|
+
"oidc_token_replayed",
|
|
2563
|
+
"oidc_run_already_exchanged"
|
|
2505
2564
|
];
|
|
2506
2565
|
var RUNNER_OPEN_MODE_TOLERATED = [
|
|
2507
2566
|
"bundle_unavailable",
|
|
@@ -2747,7 +2806,9 @@ var DURABLE_EVENT_KINDS = [
|
|
|
2747
2806
|
"session.waiting",
|
|
2748
2807
|
"session.wait_cleared",
|
|
2749
2808
|
"executor.turn_continued",
|
|
2750
|
-
"inbox.acked"
|
|
2809
|
+
"inbox.acked",
|
|
2810
|
+
// S1 amendment 64: dispatch parked a seal (api-written; session.waiting stays executor-only)
|
|
2811
|
+
"session.dispatch_parked"
|
|
2751
2812
|
];
|
|
2752
2813
|
function isDurableEventKind(v) {
|
|
2753
2814
|
return typeof v === "string" && DURABLE_EVENT_KINDS.includes(v);
|
|
@@ -2756,7 +2817,8 @@ var AMENDMENT_EVENT_GROUPS = Object.freeze({
|
|
|
2756
2817
|
"session.ready": "lifecycle",
|
|
2757
2818
|
"session.waiting": "lifecycle",
|
|
2758
2819
|
"session.wait_cleared": "lifecycle",
|
|
2759
|
-
"executor.turn_continued": "lease"
|
|
2820
|
+
"executor.turn_continued": "lease",
|
|
2821
|
+
"session.dispatch_parked": "lifecycle"
|
|
2760
2822
|
});
|
|
2761
2823
|
function eventGroupOf(kind) {
|
|
2762
2824
|
if (Object.prototype.hasOwnProperty.call(AMENDMENT_EVENT_GROUPS, kind))
|
|
@@ -3006,10 +3068,16 @@ var KIND_REFS = {
|
|
|
3006
3068
|
[
|
|
3007
3069
|
{ field: "wait_reason", equals: "ci", require: ["head_sha"], forbid: ["approval_id"] },
|
|
3008
3070
|
{ field: "wait_reason", equals: "question", require: ["approval_id"], forbid: ["head_sha"] },
|
|
3009
|
-
{ field: "wait_reason", equals: "budget", require: ["approval_id"], forbid: ["head_sha"] }
|
|
3071
|
+
{ field: "wait_reason", equals: "budget", require: ["approval_id"], forbid: ["head_sha"] },
|
|
3072
|
+
// amendment 64: a dispatch park carries no commitment
|
|
3073
|
+
{ field: "wait_reason", equals: "awaiting_issue_run", forbid: ["head_sha", "approval_id"] },
|
|
3074
|
+
{ field: "wait_reason", equals: "repo_arm_unreadable", forbid: ["head_sha", "approval_id"] }
|
|
3010
3075
|
]
|
|
3011
3076
|
),
|
|
3012
3077
|
"session.wait_cleared": obj({ wait_reason: ENUM(PARKED_ONLY_WAIT_REASONS) }),
|
|
3078
|
+
// S1 amendment 64: the api's dispatch park. One writer per kind: the executor's park stays
|
|
3079
|
+
// session.waiting; adopt and flip-to-runner journal session.wait_cleared + session.ready.
|
|
3080
|
+
"session.dispatch_parked": obj({ wait_reason: ENUM(DISPATCH_PARK_WAIT_REASONS) }),
|
|
3013
3081
|
// ---- lead ruling 2026-10-28: the spawn that sent the continue; a separate fact, so
|
|
3014
3082
|
// executor.spawned's refs stay exactly {spawn_id} ----
|
|
3015
3083
|
"executor.turn_continued": obj({ spawn_id: UUID }),
|
|
@@ -4341,12 +4409,14 @@ async function approvalSubjectHash(subject) {
|
|
|
4341
4409
|
return sha256Hex(canonicalJson(subject));
|
|
4342
4410
|
}
|
|
4343
4411
|
var AGENT_SESSIONS_READY_CHECK = "state <> 'ready' OR (durable_mode = 'locked' AND state_reason IS NULL AND home_executor_id IS NULL AND lease_executor_id IS NULL AND lease_expires_at IS NULL AND spawn_id IS NULL AND hello_at IS NULL)";
|
|
4344
|
-
var AGENT_SESSIONS_WAIT_PARKED_CHECK = "CASE WHEN wait_reason IN ('ci','question','budget') THEN state = 'parked' AND wait_ref IS NOT NULL ELSE wait_ref IS NULL END";
|
|
4412
|
+
var AGENT_SESSIONS_WAIT_PARKED_CHECK = "CASE WHEN wait_reason IN ('ci','question','budget') THEN state = 'parked' AND wait_ref IS NOT NULL WHEN wait_reason IN ('awaiting_issue_run','repo_arm_unreadable') THEN state = 'parked' AND wait_ref IS NULL ELSE wait_ref IS NULL END";
|
|
4345
4413
|
var AGENT_EXECUTORS_SCOPE_CHECK = "scope IN ('user','org')";
|
|
4346
4414
|
function readyRowOk(row) {
|
|
4347
4415
|
return row.state !== "ready" || row.durable_mode === "locked" && row.state_reason === null && row.home_executor_id === null && row.lease_executor_id === null && row.lease_expires_at === null && row.spawn_id === null && row.hello_at === null;
|
|
4348
4416
|
}
|
|
4349
4417
|
function waitParkedRowOk(row) {
|
|
4418
|
+
if (row.wait_reason !== null && DISPATCH_PARK_WAIT_REASONS.includes(row.wait_reason))
|
|
4419
|
+
return row.state === "parked" && row.wait_ref === null;
|
|
4350
4420
|
return row.wait_reason !== null && PARKED_ONLY_WAIT_REASONS.includes(row.wait_reason) ? row.state === "parked" && row.wait_ref !== null : row.wait_ref === null;
|
|
4351
4421
|
}
|
|
4352
4422
|
|
|
@@ -4852,6 +4922,16 @@ function executorStateOk(v) {
|
|
|
4852
4922
|
}
|
|
4853
4923
|
|
|
4854
4924
|
// ../shared/durable-contract/src/routes.ts
|
|
4925
|
+
var ADOPT_BODY_KEYS = ["source_ref", "nonce"];
|
|
4926
|
+
var ADOPT_SOURCE_REF_MAX = 500;
|
|
4927
|
+
function adoptBodyOk(v) {
|
|
4928
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
|
|
4929
|
+
const b = v;
|
|
4930
|
+
if (Object.keys(b).length !== 2 || typeof b.source_ref !== "string") return false;
|
|
4931
|
+
if (typeof b.nonce !== "string" || b.nonce.length === 0) return false;
|
|
4932
|
+
const r = b.source_ref;
|
|
4933
|
+
return r !== "" && r === r.trim() && r.length <= ADOPT_SOURCE_REF_MAX;
|
|
4934
|
+
}
|
|
4855
4935
|
var fixed = (method, auth, path) => Object.freeze({ method, auth, path });
|
|
4856
4936
|
var scoped = (method, auth, pre, post) => Object.freeze({ method, auth, pre, post });
|
|
4857
4937
|
var EXECUTORS = "/v1/executors";
|
|
@@ -4882,8 +4962,21 @@ var ROUTES = Object.freeze({
|
|
|
4882
4962
|
// ---- Doors amendment (ruling 34.03) ----
|
|
4883
4963
|
/** The org-scope executor's claimable pool sessions, oldest first; `?limit=` (READY_SESSIONS_LIMIT_MIN..MAX) is not part of the match. */
|
|
4884
4964
|
exec_ready_list: fixed("GET", "executor_key", "/v1/executor/ready"),
|
|
4885
|
-
/**
|
|
4965
|
+
/**
|
|
4966
|
+
* Body {oidc_token, adopt_ticket?} (doors.ts oidcExchangeBodyOk, Amendment 66). adopt_ticket comes
|
|
4967
|
+
* only from a ticketed wake-up; it is used up before any key is minted and binds the key to one seal.
|
|
4968
|
+
* Amendment 65: single-use per token jti and per run attempt (token_replayed, run_already_exchanged).
|
|
4969
|
+
*/
|
|
4886
4970
|
exec_oidc_exchange: fixed("POST", "github_oidc", "/v1/executor/oidc/exchange"),
|
|
4971
|
+
// ---- S1 amendment 64 (mnemom-test#1336) ----
|
|
4972
|
+
/**
|
|
4973
|
+
* Body {source_ref, nonce} (adoptBodyOk). The OIDC run's key unparks the parked dispatch seal whose
|
|
4974
|
+
* source_ref is this run's own issue, in its own org and repo, and claims it in one transaction,
|
|
4975
|
+
* and only the seal its adopt ticket was minted for (Amendment 66; a key with no ticket adopts none),
|
|
4976
|
+
* fenced on the nonce (exec_claim's rule);
|
|
4977
|
+
* answers the exec_claim answer, or adopt_refused / seal_repo_mismatch, changing nothing.
|
|
4978
|
+
*/
|
|
4979
|
+
exec_adopt: fixed("POST", "executor_key", "/v1/executor/adopt"),
|
|
4887
4980
|
// ---- inbox, executor side (S1 amendment 60) ----
|
|
4888
4981
|
/** Content-free summary {admitted, delivered, oldest_admitted_at} (inboxSummaryResponseOk); marks nothing. */
|
|
4889
4982
|
exec_inbox: scoped("GET", "executor_key", EXEC_SESSION_PRE, "/inbox"),
|
|
@@ -5807,8 +5900,22 @@ var FREEZE_RECORD = Object.freeze({
|
|
|
5807
5900
|
DEFAULT_LEASE_CAP_ENV,
|
|
5808
5901
|
DISPATCH_SESSION_CID_PREFIX,
|
|
5809
5902
|
DISPATCH_SESSION_NAME_PREFIX,
|
|
5903
|
+
// amendment 66
|
|
5904
|
+
ADOPT_TICKET_INPUT,
|
|
5905
|
+
WAKEUP_SESSION_INPUT,
|
|
5906
|
+
ADOPT_TICKET_PATTERN: ADOPT_TICKET_RE.source,
|
|
5907
|
+
ADOPT_TICKET_TTL_S,
|
|
5908
|
+
OIDC_TOKEN_MAX,
|
|
5909
|
+
OIDC_WORKFLOW_ENV,
|
|
5910
|
+
OIDC_WORKFLOW_FILE_PATTERN: OIDC_WORKFLOW_FILE_RE.source,
|
|
5810
5911
|
READY_SESSIONS_LIMIT_MIN,
|
|
5811
5912
|
READY_SESSIONS_LIMIT_MAX,
|
|
5913
|
+
// amendment 64
|
|
5914
|
+
ADOPT_SOURCE_REF_MAX,
|
|
5915
|
+
// amendment 65
|
|
5916
|
+
OIDC_JTI_MAX,
|
|
5917
|
+
OIDC_RUN_NUMBER_PATTERN: OIDC_RUN_NUMBER_RE.source,
|
|
5918
|
+
OIDC_EXCHANGE_RETENTION_DAYS,
|
|
5812
5919
|
READY_USER_TEXT,
|
|
5813
5920
|
RUNNER_FAILED_PHASE_PATTERN: RUNNER_FAILED_PHASE_RE.source,
|
|
5814
5921
|
AGENT_SESSIONS_READY_CHECK,
|
|
@@ -5921,11 +6028,17 @@ var FREEZE_STRUCTURED = Object.freeze({
|
|
|
5921
6028
|
READY_CAUSES,
|
|
5922
6029
|
WAIT_REASONS,
|
|
5923
6030
|
PARKED_ONLY_WAIT_REASONS,
|
|
6031
|
+
// amendment 64
|
|
6032
|
+
DISPATCH_PARK_WAIT_REASONS,
|
|
6033
|
+
ADOPT_REFUSED_CAUSES,
|
|
6034
|
+
ADOPT_BODY_KEYS,
|
|
5924
6035
|
WAIT_REF_KEYS,
|
|
5925
6036
|
CLEAR_WAIT_OUTCOMES,
|
|
5926
6037
|
EXECUTOR_SCOPES,
|
|
5927
6038
|
ORG_SCOPE_EXECUTOR_KINDS,
|
|
5928
6039
|
OIDC_EXCHANGE_REFUSALS,
|
|
6040
|
+
OIDC_EXCHANGE_BODY_KEYS,
|
|
6041
|
+
OIDC_EXCHANGE_RUN_CLAIMS,
|
|
5929
6042
|
ORG_POLICY_LIMIT_KEYS,
|
|
5930
6043
|
POOL_CLAIM_FIELDS,
|
|
5931
6044
|
CREATE_READY_SESSION_REFUSALS,
|
|
@@ -6234,6 +6347,12 @@ export {
|
|
|
6234
6347
|
ADMISSION_STEP_CODE,
|
|
6235
6348
|
ADMIT_SWEEP_VALUES,
|
|
6236
6349
|
ADMIT_SWEEP_VECTORS_PATH,
|
|
6350
|
+
ADOPT_BODY_KEYS,
|
|
6351
|
+
ADOPT_REFUSED_CAUSES,
|
|
6352
|
+
ADOPT_SOURCE_REF_MAX,
|
|
6353
|
+
ADOPT_TICKET_INPUT,
|
|
6354
|
+
ADOPT_TICKET_RE,
|
|
6355
|
+
ADOPT_TICKET_TTL_S,
|
|
6237
6356
|
ADOPT_WITH_YOU_TEXT,
|
|
6238
6357
|
AGENT_CONTAINED_STATUSES,
|
|
6239
6358
|
AGENT_EXECUTORS_SCOPE_CHECK,
|
|
@@ -6315,6 +6434,7 @@ export {
|
|
|
6315
6434
|
DEVICE_STEP_VERBS,
|
|
6316
6435
|
DEVICE_VERIFY_AAL_DEFAULT,
|
|
6317
6436
|
DEVICE_VERIFY_AAL_VALUES,
|
|
6437
|
+
DISPATCH_PARK_WAIT_REASONS,
|
|
6318
6438
|
DISPATCH_SESSION_CID_PREFIX,
|
|
6319
6439
|
DISPATCH_SESSION_NAME_PREFIX,
|
|
6320
6440
|
DONE_CLAUSE,
|
|
@@ -6487,7 +6607,15 @@ export {
|
|
|
6487
6607
|
NON_TTY_LOCKED_TEMPLATE,
|
|
6488
6608
|
NO_M1_EMITTER_KINDS,
|
|
6489
6609
|
OIDC_EXCHANGE_AUDIENCE,
|
|
6610
|
+
OIDC_EXCHANGE_BODY_KEYS,
|
|
6490
6611
|
OIDC_EXCHANGE_REFUSALS,
|
|
6612
|
+
OIDC_EXCHANGE_RETENTION_DAYS,
|
|
6613
|
+
OIDC_EXCHANGE_RUN_CLAIMS,
|
|
6614
|
+
OIDC_JTI_MAX,
|
|
6615
|
+
OIDC_RUN_NUMBER_RE,
|
|
6616
|
+
OIDC_TOKEN_MAX,
|
|
6617
|
+
OIDC_WORKFLOW_ENV,
|
|
6618
|
+
OIDC_WORKFLOW_FILE_RE,
|
|
6491
6619
|
OLD_ROUTE_BRANCH,
|
|
6492
6620
|
OLD_ROUTE_REFRESH,
|
|
6493
6621
|
ORG_CAP_LOCK_PREFIX,
|
|
@@ -6665,11 +6793,14 @@ export {
|
|
|
6665
6793
|
UUID_RE,
|
|
6666
6794
|
WAIT_REASONS,
|
|
6667
6795
|
WAIT_REF_KEYS,
|
|
6796
|
+
WAKEUP_SESSION_INPUT,
|
|
6668
6797
|
admissionStatusOf,
|
|
6669
6798
|
admissionTruthTable,
|
|
6670
6799
|
admit,
|
|
6671
6800
|
admitSweepDigest,
|
|
6672
6801
|
admitSweepText,
|
|
6802
|
+
adoptBodyOk,
|
|
6803
|
+
adoptTicketOk,
|
|
6673
6804
|
amendDecision,
|
|
6674
6805
|
answerFromChoice,
|
|
6675
6806
|
answersOk,
|
|
@@ -6797,6 +6928,7 @@ export {
|
|
|
6797
6928
|
isValidSessionName,
|
|
6798
6929
|
isValidStateReason,
|
|
6799
6930
|
isWellFormedLeaseKey,
|
|
6931
|
+
jobWorkflowRefOk,
|
|
6800
6932
|
lagBucket,
|
|
6801
6933
|
lintDurableOnly,
|
|
6802
6934
|
lintFixCatalogue,
|
|
@@ -6808,6 +6940,9 @@ export {
|
|
|
6808
6940
|
normalizeCellId,
|
|
6809
6941
|
normalizeQuestions,
|
|
6810
6942
|
normalizeSessionName,
|
|
6943
|
+
oidcExchangeBodyOk,
|
|
6944
|
+
oidcPinnedWorkflow,
|
|
6945
|
+
oidcRunClaimsOk,
|
|
6811
6946
|
openContract,
|
|
6812
6947
|
ownerSessionViewOk,
|
|
6813
6948
|
ownerVerbDecision,
|
|
@@ -15,6 +15,14 @@ export type ClaimUnavailableCause = (typeof CLAIM_UNAVAILABLE_CAUSES)[number];
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const SEAL_REPO_MISMATCH_CAUSES: readonly ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
|
|
17
17
|
export type SealRepoMismatchCause = (typeof SEAL_REPO_MISMATCH_CAUSES)[number];
|
|
18
|
+
/**
|
|
19
|
+
* adopt_refused details.cause (S1 amendment 64, mnemom-test#1336; exec_adopt): no parked dispatch
|
|
20
|
+
* seal names this source_ref in the caller's own org and repo (never says whether one exists
|
|
21
|
+
* elsewhere), or the seal's session is not parked and the caller is not the OIDC run that already
|
|
22
|
+
* holds its lease. A repo or binding mismatch is seal_repo_mismatch, not this code.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ADOPT_REFUSED_CAUSES: readonly ["no_parked_seal", "not_parked"];
|
|
25
|
+
export type AdoptRefusedCause = (typeof ADOPT_REFUSED_CAUSES)[number];
|
|
18
26
|
export declare const UNSUPPORTED_FEATURES: readonly ["tool_classes", "launch_spec_change", "above_limit_raise", "remote_control"];
|
|
19
27
|
export type UnsupportedFeature = (typeof UNSUPPORTED_FEATURES)[number];
|
|
20
28
|
export declare const CWD_KINDS: readonly ["subdirectory", "not_git", "symlink_transcript"];
|
|
@@ -273,6 +281,11 @@ export declare const DURABLE_CODES: {
|
|
|
273
281
|
readonly surfaces: readonly ["api"];
|
|
274
282
|
readonly fields: readonly ["cause"];
|
|
275
283
|
};
|
|
284
|
+
readonly adopt_refused: {
|
|
285
|
+
readonly status: 409;
|
|
286
|
+
readonly surfaces: readonly ["api"];
|
|
287
|
+
readonly fields: readonly ["cause"];
|
|
288
|
+
};
|
|
276
289
|
/** Lost CAS race on (session_id, seq). */
|
|
277
290
|
readonly durable_append_conflict: {
|
|
278
291
|
readonly status: 409;
|
|
@@ -24,6 +24,13 @@ export const CLAIM_UNAVAILABLE_CAUSES = [
|
|
|
24
24
|
* another repo than the executor's (owner/name, case-insensitive).
|
|
25
25
|
*/
|
|
26
26
|
export const SEAL_REPO_MISMATCH_CAUSES = ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
|
|
27
|
+
/**
|
|
28
|
+
* adopt_refused details.cause (S1 amendment 64, mnemom-test#1336; exec_adopt): no parked dispatch
|
|
29
|
+
* seal names this source_ref in the caller's own org and repo (never says whether one exists
|
|
30
|
+
* elsewhere), or the seal's session is not parked and the caller is not the OIDC run that already
|
|
31
|
+
* holds its lease. A repo or binding mismatch is seal_repo_mismatch, not this code.
|
|
32
|
+
*/
|
|
33
|
+
export const ADOPT_REFUSED_CAUSES = ["no_parked_seal", "not_parked"];
|
|
27
34
|
export const UNSUPPORTED_FEATURES = [
|
|
28
35
|
"tool_classes",
|
|
29
36
|
"launch_spec_change",
|
|
@@ -145,6 +152,9 @@ export const DURABLE_CODES = {
|
|
|
145
152
|
// durable_claim_ineligible reason: the session is claimable, only not by this executor, and a
|
|
146
153
|
// runner never skips past it to another session. cause is a SEAL_REPO_MISMATCH_CAUSES value.
|
|
147
154
|
seal_repo_mismatch: { status: 403, surfaces: ["api"], fields: ["cause"] },
|
|
155
|
+
// S1 amendment 64 (mnemom-test#1336): exec_adopt found nothing it may unpark for this run. Nothing
|
|
156
|
+
// is written. cause is an ADOPT_REFUSED_CAUSES value.
|
|
157
|
+
adopt_refused: { status: 409, surfaces: ["api"], fields: ["cause"] },
|
|
148
158
|
// ---- api journal (C10-R0 (i), (j), (k)) ----
|
|
149
159
|
/** Lost CAS race on (session_id, seq). */
|
|
150
160
|
durable_append_conflict: { status: 409, surfaces: ["api"], fields: ["tip_seq"] },
|
|
@@ -33,10 +33,73 @@ export declare const EXECUTOR_SCOPES: readonly ["user", "org"];
|
|
|
33
33
|
export type ExecutorScope = (typeof EXECUTOR_SCOPES)[number];
|
|
34
34
|
/** Only these executor kinds may enroll with scope org. */
|
|
35
35
|
export declare const ORG_SCOPE_EXECUTOR_KINDS: readonly ["container", "fleet"];
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* exec_oidc_exchange refusals (ruling 34.03's S1 changes), each 403, checked in this order.
|
|
38
|
+
* Amendment 65 (mnemom-test#1331) appends the two single-use codes, checked in the same transaction
|
|
39
|
+
* as the key insert so a refused or faulted exchange uses nothing up:
|
|
40
|
+
* token_replayed the token's jti was already exchanged;
|
|
41
|
+
* run_already_exchanged this repository_id + run_id + run_attempt already minted a key under
|
|
42
|
+
* another jti (one key per run attempt; a rerun is a new attempt).
|
|
43
|
+
*/
|
|
44
|
+
export declare const OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off", "adopt_ticket_invalid", "token_replayed", "run_already_exchanged"];
|
|
45
|
+
/**
|
|
46
|
+
* The claims exec_oidc_exchange requires beyond repository, repository_id, ref and event_name
|
|
47
|
+
* (amendment 65). Each is a string; a token missing one, or with one out of shape, is bad_issuer.
|
|
48
|
+
*/
|
|
49
|
+
export declare const OIDC_EXCHANGE_RUN_CLAIMS: readonly ["jti", "run_id", "run_attempt"];
|
|
50
|
+
/** jti: 1 to OIDC_JTI_MAX characters. */
|
|
51
|
+
export declare const OIDC_JTI_MAX = 200;
|
|
52
|
+
/** run_id and run_attempt: a positive decimal integer, no leading zero, at most 16 digits. */
|
|
53
|
+
export declare const OIDC_RUN_NUMBER_RE: RegExp;
|
|
54
|
+
/**
|
|
55
|
+
* How long the api keeps a run's exchange record (and so refuses a second mint for it): GitHub's
|
|
56
|
+
* longest workflow-run life, 35 days. The minted key's grant is far shorter; the record must not be.
|
|
57
|
+
*/
|
|
58
|
+
export declare const OIDC_EXCHANGE_RETENTION_DAYS = 35;
|
|
59
|
+
/** True when the three run claims are present and in shape (amendment 65). */
|
|
60
|
+
export declare function oidcRunClaimsOk(claims: Readonly<Record<string, unknown>>): boolean;
|
|
38
61
|
/** The audience a GitHub OIDC token must name for exec_oidc_exchange (ruling 34.12 (5)). */
|
|
39
62
|
export declare const OIDC_EXCHANGE_AUDIENCE = "mnemom-executor";
|
|
63
|
+
/**
|
|
64
|
+
* The workflow input that carries an adopt ticket, sent beside the per-session wake-up's
|
|
65
|
+
* WAKEUP_SESSION_INPUT. Only the api's wake-up for a session parked awaiting_issue_run sends it.
|
|
66
|
+
*/
|
|
67
|
+
export declare const ADOPT_TICKET_INPUT = "adopt-ticket";
|
|
68
|
+
/** The wake-up's per-session input (mnemom-test#1329), named here so both inputs live in S1. */
|
|
69
|
+
export declare const WAKEUP_SESSION_INPUT = "session-id";
|
|
70
|
+
/** An adopt ticket: the prefix, then 32 random bytes as unpadded base64url (43 characters). */
|
|
71
|
+
export declare const ADOPT_TICKET_RE: RegExp;
|
|
72
|
+
/** A ticket is good for 15 minutes from the wake-up that minted it, and for one exchange only. */
|
|
73
|
+
export declare const ADOPT_TICKET_TTL_S = 900;
|
|
74
|
+
/** exec_oidc_exchange's body keys: oidc_token always, adopt_ticket only from a ticketed wake-up. */
|
|
75
|
+
export declare const OIDC_EXCHANGE_BODY_KEYS: readonly ["oidc_token", "adopt_ticket"];
|
|
76
|
+
/** The longest oidc_token the exchange reads. */
|
|
77
|
+
export declare const OIDC_TOKEN_MAX = 8192;
|
|
78
|
+
/**
|
|
79
|
+
* The api env var naming the one workflow file (under .github/workflows, no path) that the wake-up
|
|
80
|
+
* dispatches and the exchange pins. The exchange is dark while it is unset or not a workflow file
|
|
81
|
+
* name, whatever DURABLE_OIDC_EXCHANGE_MODE says.
|
|
82
|
+
*/
|
|
83
|
+
export declare const OIDC_WORKFLOW_ENV = "DISPATCH_WAKEUP_WORKFLOW";
|
|
84
|
+
/** A workflow file name under .github/workflows: no path, no query. */
|
|
85
|
+
export declare const OIDC_WORKFLOW_FILE_RE: RegExp;
|
|
86
|
+
/** True when `t` has an adopt ticket's shape. */
|
|
87
|
+
export declare function adoptTicketOk(t: unknown): t is string;
|
|
88
|
+
/**
|
|
89
|
+
* The exchange body: an object with exactly oidc_token (a non-empty string of at most
|
|
90
|
+
* OIDC_TOKEN_MAX) and, optionally, adopt_ticket (a string; its shape is adoptTicketOk's, and a
|
|
91
|
+
* string of the wrong shape is refused adopt_ticket_invalid, not a malformed body).
|
|
92
|
+
*/
|
|
93
|
+
export declare function oidcExchangeBodyOk(v: unknown): boolean;
|
|
94
|
+
/** The pinned workflow file from the env value, or null (then the exchange is dark). */
|
|
95
|
+
export declare function oidcPinnedWorkflow(envValue: string | undefined): string | null;
|
|
96
|
+
/**
|
|
97
|
+
* The workflow pin: the token's job_workflow_ref must be exactly
|
|
98
|
+
* `<repository>/.github/workflows/<pinned file>@refs/heads/<default branch>`, with `repository`
|
|
99
|
+
* spelled as the token's own claim. Anything else (another workflow, a reusable workflow, another
|
|
100
|
+
* branch, a tag or sha ref) is refused ref_not_allowed.
|
|
101
|
+
*/
|
|
102
|
+
export declare function jobWorkflowRefOk(jobWorkflowRef: unknown, repository: string, workflowFile: string, defaultBranch: string): boolean;
|
|
40
103
|
/** durable_org_policy.limits keys (34.05 (1)); live_lease_cap is optional. */
|
|
41
104
|
export declare const ORG_POLICY_LIMIT_KEYS: readonly ["per_session_budget_usd", "live_lease_cap"];
|
|
42
105
|
/** live_lease_cap and the registry default share this range. */
|
|
@@ -61,7 +124,7 @@ export declare function effectiveLeaseCap(orgLimits: {
|
|
|
61
124
|
export declare const DISPATCH_SESSION_CID_PREFIX = "dispatch-";
|
|
62
125
|
export declare const DISPATCH_SESSION_NAME_PREFIX = "job-";
|
|
63
126
|
export declare const CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "dispatch_owner_missing", "seal_not_found", "cell_unmapped", "charter_mismatch"];
|
|
64
|
-
export declare const CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused"];
|
|
127
|
+
export declare const CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused", "parked"];
|
|
65
128
|
/**
|
|
66
129
|
* Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
|
|
67
130
|
* api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
|
|
@@ -75,6 +138,7 @@ export declare const CREATE_READY_SESSION_ANSWERS: Readonly<{
|
|
|
75
138
|
readonly created: readonly ["outcome", "session_id"];
|
|
76
139
|
readonly existing: readonly ["outcome", "session_id"];
|
|
77
140
|
readonly refused: readonly ["outcome", "reason"];
|
|
141
|
+
readonly parked: readonly ["outcome", "session_id", "wait_reason"];
|
|
78
142
|
}>;
|
|
79
143
|
/** Whether `v` is a closed answer: its outcome's keys only, a lowercase uuid session_id or a listed reason. */
|
|
80
144
|
export declare function createReadySessionAnswerOk(v: unknown): boolean;
|
|
@@ -123,7 +187,7 @@ export declare const POOL_JOB_RETRIES_CONFIG_CAUSE = "pool_job_retries_config";
|
|
|
123
187
|
/** The org's retry count from its limits.pool_job_retries: the default when absent, null when invalid. */
|
|
124
188
|
export declare function poolJobRetriesOf(limit: unknown): number | null;
|
|
125
189
|
/** The closed refused list (34.12 (8)). */
|
|
126
|
-
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"];
|
|
190
|
+
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", "oidc_adopt_ticket_invalid", "oidc_token_replayed", "oidc_run_already_exchanged"];
|
|
127
191
|
/** Under --fail-mode open, only these may be ridden through, and only once a verified bundle is held (34.12 (2)). */
|
|
128
192
|
export declare const RUNNER_OPEN_MODE_TOLERATED: readonly ["bundle_unavailable", "job_unavailable", "api_unavailable"];
|
|
129
193
|
export declare const RUNNER_MERGE_STRATEGIES: readonly ["external", "merge_on_green"];
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { ID_RE } from "./paths.js";
|
|
8
8
|
import { UUID_RE } from "./canonical.js";
|
|
9
|
+
import { DISPATCH_PARK_WAIT_REASONS } from "./states.js";
|
|
9
10
|
// ---- Flag (ruling 34.01 (1), (2)) -----------------------------------------------------------
|
|
10
11
|
/** The api env flag that gates every pool behaviour; its value is the org allowlist. */
|
|
11
12
|
export const DURABLE_POOL_CLAIM_FLAG = "DURABLE_POOL_CLAIM";
|
|
@@ -44,15 +45,109 @@ export function poolClaimOrgs(flagValue) {
|
|
|
44
45
|
export const EXECUTOR_SCOPES = ["user", "org"];
|
|
45
46
|
/** Only these executor kinds may enroll with scope org. */
|
|
46
47
|
export const ORG_SCOPE_EXECUTOR_KINDS = ["container", "fleet"];
|
|
47
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* exec_oidc_exchange refusals (ruling 34.03's S1 changes), each 403, checked in this order.
|
|
50
|
+
* Amendment 65 (mnemom-test#1331) appends the two single-use codes, checked in the same transaction
|
|
51
|
+
* as the key insert so a refused or faulted exchange uses nothing up:
|
|
52
|
+
* token_replayed the token's jti was already exchanged;
|
|
53
|
+
* run_already_exchanged this repository_id + run_id + run_attempt already minted a key under
|
|
54
|
+
* another jti (one key per run attempt; a rerun is a new attempt).
|
|
55
|
+
*/
|
|
48
56
|
export const OIDC_EXCHANGE_REFUSALS = [
|
|
49
57
|
"bad_issuer",
|
|
50
58
|
"repo_not_enrolled",
|
|
51
59
|
"ref_not_allowed",
|
|
52
60
|
"pool_flag_off",
|
|
61
|
+
"adopt_ticket_invalid",
|
|
62
|
+
"token_replayed",
|
|
63
|
+
"run_already_exchanged",
|
|
53
64
|
];
|
|
65
|
+
/**
|
|
66
|
+
* The claims exec_oidc_exchange requires beyond repository, repository_id, ref and event_name
|
|
67
|
+
* (amendment 65). Each is a string; a token missing one, or with one out of shape, is bad_issuer.
|
|
68
|
+
*/
|
|
69
|
+
export const OIDC_EXCHANGE_RUN_CLAIMS = ["jti", "run_id", "run_attempt"];
|
|
70
|
+
/** jti: 1 to OIDC_JTI_MAX characters. */
|
|
71
|
+
export const OIDC_JTI_MAX = 200;
|
|
72
|
+
/** run_id and run_attempt: a positive decimal integer, no leading zero, at most 16 digits. */
|
|
73
|
+
export const OIDC_RUN_NUMBER_RE = /^[1-9][0-9]{0,15}$/;
|
|
74
|
+
/**
|
|
75
|
+
* How long the api keeps a run's exchange record (and so refuses a second mint for it): GitHub's
|
|
76
|
+
* longest workflow-run life, 35 days. The minted key's grant is far shorter; the record must not be.
|
|
77
|
+
*/
|
|
78
|
+
export const OIDC_EXCHANGE_RETENTION_DAYS = 35;
|
|
79
|
+
/** True when the three run claims are present and in shape (amendment 65). */
|
|
80
|
+
export function oidcRunClaimsOk(claims) {
|
|
81
|
+
const { jti, run_id, run_attempt } = claims;
|
|
82
|
+
return (typeof jti === "string" &&
|
|
83
|
+
jti.length >= 1 &&
|
|
84
|
+
jti.length <= OIDC_JTI_MAX &&
|
|
85
|
+
typeof run_id === "string" &&
|
|
86
|
+
OIDC_RUN_NUMBER_RE.test(run_id) &&
|
|
87
|
+
typeof run_attempt === "string" &&
|
|
88
|
+
OIDC_RUN_NUMBER_RE.test(run_attempt));
|
|
89
|
+
}
|
|
54
90
|
/** The audience a GitHub OIDC token must name for exec_oidc_exchange (ruling 34.12 (5)). */
|
|
55
91
|
export const OIDC_EXCHANGE_AUDIENCE = "mnemom-executor";
|
|
92
|
+
// ---- Adopt ticket and workflow pin (Amendment 66, mnemom-test#1336) -------------------------
|
|
93
|
+
/**
|
|
94
|
+
* The workflow input that carries an adopt ticket, sent beside the per-session wake-up's
|
|
95
|
+
* WAKEUP_SESSION_INPUT. Only the api's wake-up for a session parked awaiting_issue_run sends it.
|
|
96
|
+
*/
|
|
97
|
+
export const ADOPT_TICKET_INPUT = "adopt-ticket";
|
|
98
|
+
/** The wake-up's per-session input (mnemom-test#1329), named here so both inputs live in S1. */
|
|
99
|
+
export const WAKEUP_SESSION_INPUT = "session-id";
|
|
100
|
+
/** An adopt ticket: the prefix, then 32 random bytes as unpadded base64url (43 characters). */
|
|
101
|
+
export const ADOPT_TICKET_RE = /^mnm_at_[A-Za-z0-9_-]{43}$/;
|
|
102
|
+
/** A ticket is good for 15 minutes from the wake-up that minted it, and for one exchange only. */
|
|
103
|
+
export const ADOPT_TICKET_TTL_S = 900;
|
|
104
|
+
/** exec_oidc_exchange's body keys: oidc_token always, adopt_ticket only from a ticketed wake-up. */
|
|
105
|
+
export const OIDC_EXCHANGE_BODY_KEYS = ["oidc_token", "adopt_ticket"];
|
|
106
|
+
/** The longest oidc_token the exchange reads. */
|
|
107
|
+
export const OIDC_TOKEN_MAX = 8192;
|
|
108
|
+
/**
|
|
109
|
+
* The api env var naming the one workflow file (under .github/workflows, no path) that the wake-up
|
|
110
|
+
* dispatches and the exchange pins. The exchange is dark while it is unset or not a workflow file
|
|
111
|
+
* name, whatever DURABLE_OIDC_EXCHANGE_MODE says.
|
|
112
|
+
*/
|
|
113
|
+
export const OIDC_WORKFLOW_ENV = "DISPATCH_WAKEUP_WORKFLOW";
|
|
114
|
+
/** A workflow file name under .github/workflows: no path, no query. */
|
|
115
|
+
export const OIDC_WORKFLOW_FILE_RE = /^[A-Za-z0-9_.-]{1,100}\.ya?ml$/;
|
|
116
|
+
/** True when `t` has an adopt ticket's shape. */
|
|
117
|
+
export function adoptTicketOk(t) {
|
|
118
|
+
return typeof t === "string" && ADOPT_TICKET_RE.test(t);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The exchange body: an object with exactly oidc_token (a non-empty string of at most
|
|
122
|
+
* OIDC_TOKEN_MAX) and, optionally, adopt_ticket (a string; its shape is adoptTicketOk's, and a
|
|
123
|
+
* string of the wrong shape is refused adopt_ticket_invalid, not a malformed body).
|
|
124
|
+
*/
|
|
125
|
+
export function oidcExchangeBodyOk(v) {
|
|
126
|
+
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
127
|
+
return false;
|
|
128
|
+
const o = v;
|
|
129
|
+
const keys = Object.keys(o);
|
|
130
|
+
if (keys.some((k) => !OIDC_EXCHANGE_BODY_KEYS.includes(k)))
|
|
131
|
+
return false;
|
|
132
|
+
if (typeof o.oidc_token !== "string" || o.oidc_token === "" || o.oidc_token.length > OIDC_TOKEN_MAX)
|
|
133
|
+
return false;
|
|
134
|
+
return !("adopt_ticket" in o) || typeof o.adopt_ticket === "string";
|
|
135
|
+
}
|
|
136
|
+
/** The pinned workflow file from the env value, or null (then the exchange is dark). */
|
|
137
|
+
export function oidcPinnedWorkflow(envValue) {
|
|
138
|
+
const wf = envValue?.trim() ?? "";
|
|
139
|
+
return OIDC_WORKFLOW_FILE_RE.test(wf) ? wf : null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The workflow pin: the token's job_workflow_ref must be exactly
|
|
143
|
+
* `<repository>/.github/workflows/<pinned file>@refs/heads/<default branch>`, with `repository`
|
|
144
|
+
* spelled as the token's own claim. Anything else (another workflow, a reusable workflow, another
|
|
145
|
+
* branch, a tag or sha ref) is refused ref_not_allowed.
|
|
146
|
+
*/
|
|
147
|
+
export function jobWorkflowRefOk(jobWorkflowRef, repository, workflowFile, defaultBranch) {
|
|
148
|
+
return (typeof jobWorkflowRef === "string" &&
|
|
149
|
+
jobWorkflowRef === `${repository}/.github/workflows/${workflowFile}@refs/heads/${defaultBranch}`);
|
|
150
|
+
}
|
|
56
151
|
// ---- Per-org live-lease cap (rulings 34.05, 34.05.1) ----------------------------------------
|
|
57
152
|
/** durable_org_policy.limits keys (34.05 (1)); live_lease_cap is optional. */
|
|
58
153
|
export const ORG_POLICY_LIMIT_KEYS = ["per_session_budget_usd", "live_lease_cap"];
|
|
@@ -99,7 +194,9 @@ export const CREATE_READY_SESSION_REFUSALS = [
|
|
|
99
194
|
// 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
|
|
100
195
|
"charter_mismatch",
|
|
101
196
|
];
|
|
102
|
-
|
|
197
|
+
// S1 amendment 64 (mnemom-test#1336): "parked", the seal's repo does not take it through the pool;
|
|
198
|
+
// counted apart from created, and the wake-up never sends for it
|
|
199
|
+
export const CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused", "parked"];
|
|
103
200
|
/**
|
|
104
201
|
* Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
|
|
105
202
|
* api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
|
|
@@ -115,6 +212,7 @@ export const CREATE_READY_SESSION_ANSWERS = Object.freeze({
|
|
|
115
212
|
created: ["outcome", "session_id"],
|
|
116
213
|
existing: ["outcome", "session_id"],
|
|
117
214
|
refused: ["outcome", "reason"],
|
|
215
|
+
parked: ["outcome", "session_id", "wait_reason"],
|
|
118
216
|
});
|
|
119
217
|
/** Whether `v` is a closed answer: its outcome's keys only, a lowercase uuid session_id or a listed reason. */
|
|
120
218
|
export function createReadySessionAnswerOk(v) {
|
|
@@ -127,9 +225,12 @@ export function createReadySessionAnswerOk(v) {
|
|
|
127
225
|
const keys = CREATE_READY_SESSION_ANSWERS[a.outcome];
|
|
128
226
|
if (Object.keys(a).length !== keys.length || !keys.every((k) => k in a))
|
|
129
227
|
return false;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
228
|
+
if (a.outcome === "refused")
|
|
229
|
+
return CREATE_READY_SESSION_REFUSALS.includes(a.reason);
|
|
230
|
+
if (a.outcome === "parked" &&
|
|
231
|
+
!DISPATCH_PARK_WAIT_REASONS.includes(a.wait_reason))
|
|
232
|
+
return false;
|
|
233
|
+
return typeof a.session_id === "string" && UUID_RE.test(a.session_id);
|
|
133
234
|
}
|
|
134
235
|
function sealIdOk(sealId) {
|
|
135
236
|
if (!UUID_RE.test(sealId))
|
|
@@ -238,6 +339,10 @@ export const RUNNER_REFUSED_REASONS = [
|
|
|
238
339
|
"oidc_pool_flag_off",
|
|
239
340
|
"oidc_unavailable",
|
|
240
341
|
"api_unavailable",
|
|
342
|
+
"oidc_adopt_ticket_invalid",
|
|
343
|
+
// amendment 65 (mnemom-test#1331): the two single-use exchange refusals, appended
|
|
344
|
+
"oidc_token_replayed",
|
|
345
|
+
"oidc_run_already_exchanged",
|
|
241
346
|
];
|
|
242
347
|
/** Under --fail-mode open, only these may be ridden through, and only once a verified bundle is held (34.12 (2)). */
|
|
243
348
|
export const RUNNER_OPEN_MODE_TOLERATED = [
|
|
@@ -34,7 +34,7 @@ export declare const M1_EXCLUDED_KINDS: readonly ["intent.issued", "intent.commi
|
|
|
34
34
|
* inbox ack writes it; M1_EXCLUDED_KINDS stays the frozen M1 record), appended so no existing index
|
|
35
35
|
* moves. Mirrors durable_event_kinds (parity test).
|
|
36
36
|
*/
|
|
37
|
-
export declare const DURABLE_EVENT_KINDS: readonly ["session.registered", "session.locked", "session.unlocked", "session.resumed", "session.detached", "session.parked", "session.suspended", "session.paused", "session.unpaused", "session.moved", "session.completed", "session.failed", "session.killed", "session.expired", "session.adopted", "seat.hello", "charter.sealed", "charter.amended", "charter.goal_sealed", "charter.card_drift", "profile.amended", "executor.upgraded", "attestation.passed", "attestation.failed", "lease.acquired", "lease.reclaimed", "lease.released", "lease.expired", "lease.fenced", "lease.zombie_call", "lease.stale_admit", "executor.enrolled", "executor.revoked", "executor.spawned", "executor.exited", "executor.contract_violation", "inbox.admitted", "inbox.rejected", "inbox.coalesced", "inbox.held_unsealed", "inbox.queued_for_home", "inbox.reverted", "inbox.delivered", "inbox.unmatched", "inbox.dead", "timer.set", "timer.fired", "timer.cancelled", "trigger.added", "trigger.removed", "trigger.fired", "trigger.suppressed", "trigger.over_cap", "wake.undelivered", "question.asked", "question.answered", "approval.requested", "approval.resolved", "approval.consumed", "approval.expired", "approval.superseded", "approval.single_person_override", "never.held", "chain.posture", "guard.refused", "guard.unavailable", "budget.reached", "grant.renewed", "grant.revoked", "verdict.violation", "identity.mismatch", "containment.pause", "containment.kill", "containment.auto_pause", "governance.degraded_admit", "governance.outage_mode_set", "policy.host_unattended_changed", "workspace.checkpointed", "rearm.recorded", "tip.signed", "session.ready", "session.waiting", "session.wait_cleared", "executor.turn_continued", "inbox.acked"];
|
|
37
|
+
export declare const DURABLE_EVENT_KINDS: readonly ["session.registered", "session.locked", "session.unlocked", "session.resumed", "session.detached", "session.parked", "session.suspended", "session.paused", "session.unpaused", "session.moved", "session.completed", "session.failed", "session.killed", "session.expired", "session.adopted", "seat.hello", "charter.sealed", "charter.amended", "charter.goal_sealed", "charter.card_drift", "profile.amended", "executor.upgraded", "attestation.passed", "attestation.failed", "lease.acquired", "lease.reclaimed", "lease.released", "lease.expired", "lease.fenced", "lease.zombie_call", "lease.stale_admit", "executor.enrolled", "executor.revoked", "executor.spawned", "executor.exited", "executor.contract_violation", "inbox.admitted", "inbox.rejected", "inbox.coalesced", "inbox.held_unsealed", "inbox.queued_for_home", "inbox.reverted", "inbox.delivered", "inbox.unmatched", "inbox.dead", "timer.set", "timer.fired", "timer.cancelled", "trigger.added", "trigger.removed", "trigger.fired", "trigger.suppressed", "trigger.over_cap", "wake.undelivered", "question.asked", "question.answered", "approval.requested", "approval.resolved", "approval.consumed", "approval.expired", "approval.superseded", "approval.single_person_override", "never.held", "chain.posture", "guard.refused", "guard.unavailable", "budget.reached", "grant.renewed", "grant.revoked", "verdict.violation", "identity.mismatch", "containment.pause", "containment.kill", "containment.auto_pause", "governance.degraded_admit", "governance.outage_mode_set", "policy.host_unattended_changed", "workspace.checkpointed", "rearm.recorded", "tip.signed", "session.ready", "session.waiting", "session.wait_cleared", "executor.turn_continued", "inbox.acked", "session.dispatch_parked"];
|
|
38
38
|
export type DurableEventKind = (typeof DURABLE_EVENT_KINDS)[number];
|
|
39
39
|
export declare function isDurableEventKind(v: unknown): v is DurableEventKind;
|
|
40
40
|
/**
|
|
@@ -48,6 +48,7 @@ export declare const AMENDMENT_EVENT_GROUPS: Readonly<{
|
|
|
48
48
|
readonly "session.waiting": "lifecycle";
|
|
49
49
|
readonly "session.wait_cleared": "lifecycle";
|
|
50
50
|
readonly "executor.turn_continued": "lease";
|
|
51
|
+
readonly "session.dispatch_parked": "lifecycle";
|
|
51
52
|
}>;
|
|
52
53
|
export declare function eventGroupOf(kind: DurableEventKind): EventGroup;
|
|
53
54
|
export declare const EVENT_WRITERS: readonly ["executor", "gateway", "api"];
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* (m) DSR: phase user_erasure, treatment pseudonymize (nulls principal_uid, detail, refs).
|
|
23
23
|
*/
|
|
24
24
|
import { canonicalJson, hmacSha256Hex, sha256Hex, HEX64_RE } from "./canonical.js";
|
|
25
|
-
import { DETACHED_REASONS, PAUSED_REASONS, SUSPENDED_REASONS, FAILED_REASONS, PARKED_ONLY_WAIT_REASONS, } from "./states.js";
|
|
25
|
+
import { DETACHED_REASONS, PAUSED_REASONS, SUSPENDED_REASONS, FAILED_REASONS, DISPATCH_PARK_WAIT_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
28
|
import { RUNNER_FAILED_REASON_PATTERN } from "./doors.js";
|
|
@@ -246,6 +246,8 @@ export const DURABLE_EVENT_KINDS = [
|
|
|
246
246
|
"session.wait_cleared",
|
|
247
247
|
"executor.turn_continued",
|
|
248
248
|
"inbox.acked",
|
|
249
|
+
// S1 amendment 64: dispatch parked a seal (api-written; session.waiting stays executor-only)
|
|
250
|
+
"session.dispatch_parked",
|
|
249
251
|
];
|
|
250
252
|
export function isDurableEventKind(v) {
|
|
251
253
|
return typeof v === "string" && DURABLE_EVENT_KINDS.includes(v);
|
|
@@ -261,6 +263,7 @@ export const AMENDMENT_EVENT_GROUPS = Object.freeze({
|
|
|
261
263
|
"session.waiting": "lifecycle",
|
|
262
264
|
"session.wait_cleared": "lifecycle",
|
|
263
265
|
"executor.turn_continued": "lease",
|
|
266
|
+
"session.dispatch_parked": "lifecycle",
|
|
264
267
|
});
|
|
265
268
|
export function eventGroupOf(kind) {
|
|
266
269
|
if (Object.prototype.hasOwnProperty.call(AMENDMENT_EVENT_GROUPS, kind))
|
|
@@ -500,8 +503,14 @@ const KIND_REFS = {
|
|
|
500
503
|
{ field: "wait_reason", equals: "ci", require: ["head_sha"], forbid: ["approval_id"] },
|
|
501
504
|
{ field: "wait_reason", equals: "question", require: ["approval_id"], forbid: ["head_sha"] },
|
|
502
505
|
{ field: "wait_reason", equals: "budget", require: ["approval_id"], forbid: ["head_sha"] },
|
|
506
|
+
// amendment 64: a dispatch park carries no commitment
|
|
507
|
+
{ field: "wait_reason", equals: "awaiting_issue_run", forbid: ["head_sha", "approval_id"] },
|
|
508
|
+
{ field: "wait_reason", equals: "repo_arm_unreadable", forbid: ["head_sha", "approval_id"] },
|
|
503
509
|
]),
|
|
504
510
|
"session.wait_cleared": obj({ wait_reason: ENUM(PARKED_ONLY_WAIT_REASONS) }),
|
|
511
|
+
// S1 amendment 64: the api's dispatch park. One writer per kind: the executor's park stays
|
|
512
|
+
// session.waiting; adopt and flip-to-runner journal session.wait_cleared + session.ready.
|
|
513
|
+
"session.dispatch_parked": obj({ wait_reason: ENUM(DISPATCH_PARK_WAIT_REASONS) }),
|
|
505
514
|
// ---- lead ruling 2026-10-28: the spawn that sent the continue; a separate fact, so
|
|
506
515
|
// executor.spawned's refs stay exactly {spawn_id} ----
|
|
507
516
|
"executor.turn_continued": obj({ spawn_id: UUID }),
|
|
@@ -121,8 +121,19 @@ export declare const FREEZE_RECORD: Readonly<{
|
|
|
121
121
|
DEFAULT_LEASE_CAP_ENV: "DURABLE_DEFAULT_LEASE_CAP";
|
|
122
122
|
DISPATCH_SESSION_CID_PREFIX: "dispatch-";
|
|
123
123
|
DISPATCH_SESSION_NAME_PREFIX: "job-";
|
|
124
|
+
ADOPT_TICKET_INPUT: "adopt-ticket";
|
|
125
|
+
WAKEUP_SESSION_INPUT: "session-id";
|
|
126
|
+
ADOPT_TICKET_PATTERN: string;
|
|
127
|
+
ADOPT_TICKET_TTL_S: 900;
|
|
128
|
+
OIDC_TOKEN_MAX: 8192;
|
|
129
|
+
OIDC_WORKFLOW_ENV: "DISPATCH_WAKEUP_WORKFLOW";
|
|
130
|
+
OIDC_WORKFLOW_FILE_PATTERN: string;
|
|
124
131
|
READY_SESSIONS_LIMIT_MIN: 1;
|
|
125
132
|
READY_SESSIONS_LIMIT_MAX: 50;
|
|
133
|
+
ADOPT_SOURCE_REF_MAX: 500;
|
|
134
|
+
OIDC_JTI_MAX: 200;
|
|
135
|
+
OIDC_RUN_NUMBER_PATTERN: string;
|
|
136
|
+
OIDC_EXCHANGE_RETENTION_DAYS: 35;
|
|
126
137
|
READY_USER_TEXT: "waiting for a runner";
|
|
127
138
|
RUNNER_FAILED_PHASE_PATTERN: string;
|
|
128
139
|
AGENT_SESSIONS_READY_CHECK: string;
|
|
@@ -330,6 +341,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
330
341
|
exec_heartbeat: import("./routes.js").FixedRoute;
|
|
331
342
|
exec_ready_list: import("./routes.js").FixedRoute;
|
|
332
343
|
exec_oidc_exchange: import("./routes.js").FixedRoute;
|
|
344
|
+
exec_adopt: import("./routes.js").FixedRoute;
|
|
333
345
|
exec_inbox: import("./routes.js").ScopedRoute;
|
|
334
346
|
exec_inbox_take: import("./routes.js").ScopedRoute;
|
|
335
347
|
exec_inbox_ack: import("./routes.js").ScopedRoute;
|
|
@@ -724,8 +736,11 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
724
736
|
}>;
|
|
725
737
|
GATEWAY_EVENTS_BODY_KEYS: readonly ["session_id", "kind", "epoch", "idempotency_key", "refs"];
|
|
726
738
|
READY_CAUSES: readonly ["dispatched", "lease_expired", "wait_cleared", "job_failed"];
|
|
727
|
-
WAIT_REASONS: readonly ["approval", "moving", "mnemom_unavailable", "ci", "question", "budget"];
|
|
728
|
-
PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget"];
|
|
739
|
+
WAIT_REASONS: readonly ["approval", "moving", "mnemom_unavailable", "ci", "question", "budget", "awaiting_issue_run", "repo_arm_unreadable"];
|
|
740
|
+
PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget", "awaiting_issue_run", "repo_arm_unreadable"];
|
|
741
|
+
DISPATCH_PARK_WAIT_REASONS: readonly ["awaiting_issue_run", "repo_arm_unreadable"];
|
|
742
|
+
ADOPT_REFUSED_CAUSES: readonly ["no_parked_seal", "not_parked"];
|
|
743
|
+
ADOPT_BODY_KEYS: readonly ["source_ref", "nonce"];
|
|
729
744
|
WAIT_REF_KEYS: Readonly<{
|
|
730
745
|
readonly ci: readonly ["repo_id", "head_sha"];
|
|
731
746
|
readonly question: readonly ["approval_id"];
|
|
@@ -734,15 +749,18 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
734
749
|
CLEAR_WAIT_OUTCOMES: readonly ["cleared", "stale_ref", "already_clear"];
|
|
735
750
|
EXECUTOR_SCOPES: readonly ["user", "org"];
|
|
736
751
|
ORG_SCOPE_EXECUTOR_KINDS: readonly ["container", "fleet"];
|
|
737
|
-
OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off"];
|
|
752
|
+
OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off", "adopt_ticket_invalid", "token_replayed", "run_already_exchanged"];
|
|
753
|
+
OIDC_EXCHANGE_BODY_KEYS: readonly ["oidc_token", "adopt_ticket"];
|
|
754
|
+
OIDC_EXCHANGE_RUN_CLAIMS: readonly ["jti", "run_id", "run_attempt"];
|
|
738
755
|
ORG_POLICY_LIMIT_KEYS: readonly ["per_session_budget_usd", "live_lease_cap"];
|
|
739
756
|
POOL_CLAIM_FIELDS: readonly ["contract_sha256", "contract_version", "cap"];
|
|
740
757
|
CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "dispatch_owner_missing", "seal_not_found", "cell_unmapped", "charter_mismatch"];
|
|
741
|
-
CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused"];
|
|
758
|
+
CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused", "parked"];
|
|
742
759
|
CREATE_READY_SESSION_ANSWERS: Readonly<{
|
|
743
760
|
readonly created: readonly ["outcome", "session_id"];
|
|
744
761
|
readonly existing: readonly ["outcome", "session_id"];
|
|
745
762
|
readonly refused: readonly ["outcome", "reason"];
|
|
763
|
+
readonly parked: readonly ["outcome", "session_id", "wait_reason"];
|
|
746
764
|
}>;
|
|
747
765
|
JOURNAL_APPROVAL_KINDS: readonly ["approval.resolved", "approval.consumed"];
|
|
748
766
|
RUNNER_OUTCOMES: readonly ["done", "no_job", "failed", "refused"];
|
|
@@ -755,7 +773,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
755
773
|
readonly refused: 3;
|
|
756
774
|
}>;
|
|
757
775
|
RUNNER_FAILED_REASONS: readonly ["pr_open_failed", "pr_no_changes"];
|
|
758
|
-
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"];
|
|
776
|
+
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", "oidc_adopt_ticket_invalid", "oidc_token_replayed", "oidc_run_already_exchanged"];
|
|
759
777
|
RUNNER_OPEN_MODE_TOLERATED: readonly ["bundle_unavailable", "job_unavailable", "api_unavailable"];
|
|
760
778
|
RUNNER_MERGE_STRATEGIES: readonly ["external", "merge_on_green"];
|
|
761
779
|
}>;
|
|
@@ -15,15 +15,15 @@ import * as C from "./constants.js";
|
|
|
15
15
|
import { API_CELL_ID_VECTORS_SHA256, CANONICAL_CELL_IDS } from "./cell-id.js";
|
|
16
16
|
import { LEASE_KEY_PREFIX } from "./keys.js";
|
|
17
17
|
import { ARM_HEADER, ARM_VERSION } from "./arm.js";
|
|
18
|
-
import { CHARTER_BY_HASH_BODY_KEYS, CHARTER_BY_HASH_RESPONSE, CHARTER_SEALED_AT_RE, CLAIM_BODY_KEYS, EXEC_SESSIONS_RESPONSE, OWNER_SESSION_VIEW_FIELDS, GATEWAY_EVENTS_BODY_KEYS, EXECUTOR_KEY_HEADER, HEARTBEAT_REQUEST_KEYS, INTERNAL_KEY_HEADER, ROUTES, } from "./routes.js";
|
|
18
|
+
import { CHARTER_BY_HASH_BODY_KEYS, CHARTER_BY_HASH_RESPONSE, CHARTER_SEALED_AT_RE, CLAIM_BODY_KEYS, EXEC_SESSIONS_RESPONSE, OWNER_SESSION_VIEW_FIELDS, GATEWAY_EVENTS_BODY_KEYS, EXECUTOR_KEY_HEADER, HEARTBEAT_REQUEST_KEYS, INTERNAL_KEY_HEADER, ROUTES, ADOPT_BODY_KEYS, ADOPT_SOURCE_REF_MAX, } from "./routes.js";
|
|
19
19
|
import * as D from "./doors.js";
|
|
20
20
|
import { JOURNAL_APPROVAL_KINDS } from "./journal-approvals.js";
|
|
21
|
-
import { PARKED_ONLY_WAIT_REASONS, READY_USER_TEXT, WAIT_REASONS } from "./states.js";
|
|
21
|
+
import { DISPATCH_PARK_WAIT_REASONS, PARKED_ONLY_WAIT_REASONS, READY_USER_TEXT, WAIT_REASONS, } from "./states.js";
|
|
22
22
|
import { SECRET_SCOPE } from "./secret-scope.js";
|
|
23
23
|
import { EXECUTOR_STATE_DIR, EXECUTOR_STATE_DIR_MODE, EXECUTOR_STATE_FILE, EXECUTOR_STATE_FILE_MODE, } from "./paths.js";
|
|
24
24
|
import { ATTRIBUTE_VALUES, DEVICE_VERIFY_AAL_DEFAULT, M1_SPAN_NAMES, SPAN_ATTRIBUTE_KEYS, SPAN_DEVICE_VERIFY_WOULD_REFUSE, SPAN_DURABLE_ADMISSION, SPAN_DURABLE_CLAIM, SPAN_DURABLE_ENROLLMENT, SPAN_DURABLE_SEAT_HELLO, SPAN_DURABLE_SWEEP, SWEEP_LOG_NAME, } from "./telemetry.js";
|
|
25
25
|
import { EVENT_PREIMAGE_FIELDS, EXECUTOR_WRITABLE_KINDS, GATEWAY_EVENTS_ROUTE, GATEWAY_WRITABLE_KINDS, GENESIS_PREV_HASH, READY_CAUSES, TERMINAL_EVENT_KINDS, } from "./events.js";
|
|
26
|
-
import { CLAIM_INELIGIBLE_DETAILS, CLAIM_INELIGIBLE_REASONS, CLAIM_LOST_RACE_REASONS, CLAIM_OUTCOMES, DURABLE_CODES, PARK_REFUSAL_REASONS, HEARTBEAT_ENTRY_FIELDS, HEARTBEAT_RESULT_FIELDS, ENROLL_POLL_ERROR_STATUS, ENROLL_POLL_ERRORS, } from "./codes.js";
|
|
26
|
+
import { CLAIM_INELIGIBLE_DETAILS, CLAIM_INELIGIBLE_REASONS, CLAIM_LOST_RACE_REASONS, CLAIM_OUTCOMES, DURABLE_CODES, ADOPT_REFUSED_CAUSES, PARK_REFUSAL_REASONS, HEARTBEAT_ENTRY_FIELDS, HEARTBEAT_RESULT_FIELDS, ENROLL_POLL_ERROR_STATUS, ENROLL_POLL_ERRORS, } from "./codes.js";
|
|
27
27
|
import { DEVICE_PAGE_MARKERS, FIX_LINES, RISK_LINE_TEXT, RISK_SENTENCE_ID_HOST, STOPPED_FIX, STOPPED_REASON_TEXT, U05_CHECK_TEXT_ATTESTATION_FAILED, U05_CHECK_TEXT_RESPAWN_FAILED, U05_FIXTURE_CHECK, } from "./fixlines.js";
|
|
28
28
|
import { PLUGIN_DIR, PLUGIN_MANIFEST_FAILURES, PLUGIN_MANIFEST_PATH, PLUGIN_MANIFEST_SCHEMA, } from "./spec.js";
|
|
29
29
|
import { AGENT_EXECUTORS_SCOPE_CHECK, AGENT_SESSIONS_READY_CHECK, AGENT_SESSIONS_WAIT_PARKED_CHECK, DURABLE_APPROVAL_SESSIONLESS_SUBJECTS, DURABLE_APPROVAL_SUBJECTS, DURABLE_APPROVALS_TARGET_CHECK, EXECUTE_EXCEPTIONS, M1_ARBITERS, M1_COLUMNS, M1_FUNCTIONS, M1_TABLES, } from "./schema.js";
|
|
@@ -151,8 +151,22 @@ export const FREEZE_RECORD = Object.freeze({
|
|
|
151
151
|
DEFAULT_LEASE_CAP_ENV: D.DEFAULT_LEASE_CAP_ENV,
|
|
152
152
|
DISPATCH_SESSION_CID_PREFIX: D.DISPATCH_SESSION_CID_PREFIX,
|
|
153
153
|
DISPATCH_SESSION_NAME_PREFIX: D.DISPATCH_SESSION_NAME_PREFIX,
|
|
154
|
+
// amendment 66
|
|
155
|
+
ADOPT_TICKET_INPUT: D.ADOPT_TICKET_INPUT,
|
|
156
|
+
WAKEUP_SESSION_INPUT: D.WAKEUP_SESSION_INPUT,
|
|
157
|
+
ADOPT_TICKET_PATTERN: D.ADOPT_TICKET_RE.source,
|
|
158
|
+
ADOPT_TICKET_TTL_S: D.ADOPT_TICKET_TTL_S,
|
|
159
|
+
OIDC_TOKEN_MAX: D.OIDC_TOKEN_MAX,
|
|
160
|
+
OIDC_WORKFLOW_ENV: D.OIDC_WORKFLOW_ENV,
|
|
161
|
+
OIDC_WORKFLOW_FILE_PATTERN: D.OIDC_WORKFLOW_FILE_RE.source,
|
|
154
162
|
READY_SESSIONS_LIMIT_MIN: D.READY_SESSIONS_LIMIT_MIN,
|
|
155
163
|
READY_SESSIONS_LIMIT_MAX: D.READY_SESSIONS_LIMIT_MAX,
|
|
164
|
+
// amendment 64
|
|
165
|
+
ADOPT_SOURCE_REF_MAX,
|
|
166
|
+
// amendment 65
|
|
167
|
+
OIDC_JTI_MAX: D.OIDC_JTI_MAX,
|
|
168
|
+
OIDC_RUN_NUMBER_PATTERN: D.OIDC_RUN_NUMBER_RE.source,
|
|
169
|
+
OIDC_EXCHANGE_RETENTION_DAYS: D.OIDC_EXCHANGE_RETENTION_DAYS,
|
|
156
170
|
READY_USER_TEXT,
|
|
157
171
|
RUNNER_FAILED_PHASE_PATTERN: D.RUNNER_FAILED_PHASE_RE.source,
|
|
158
172
|
AGENT_SESSIONS_READY_CHECK,
|
|
@@ -265,11 +279,17 @@ export const FREEZE_STRUCTURED = Object.freeze({
|
|
|
265
279
|
READY_CAUSES,
|
|
266
280
|
WAIT_REASONS,
|
|
267
281
|
PARKED_ONLY_WAIT_REASONS,
|
|
282
|
+
// amendment 64
|
|
283
|
+
DISPATCH_PARK_WAIT_REASONS,
|
|
284
|
+
ADOPT_REFUSED_CAUSES,
|
|
285
|
+
ADOPT_BODY_KEYS,
|
|
268
286
|
WAIT_REF_KEYS: D.WAIT_REF_KEYS,
|
|
269
287
|
CLEAR_WAIT_OUTCOMES: D.CLEAR_WAIT_OUTCOMES,
|
|
270
288
|
EXECUTOR_SCOPES: D.EXECUTOR_SCOPES,
|
|
271
289
|
ORG_SCOPE_EXECUTOR_KINDS: D.ORG_SCOPE_EXECUTOR_KINDS,
|
|
272
290
|
OIDC_EXCHANGE_REFUSALS: D.OIDC_EXCHANGE_REFUSALS,
|
|
291
|
+
OIDC_EXCHANGE_BODY_KEYS: D.OIDC_EXCHANGE_BODY_KEYS,
|
|
292
|
+
OIDC_EXCHANGE_RUN_CLAIMS: D.OIDC_EXCHANGE_RUN_CLAIMS,
|
|
273
293
|
ORG_POLICY_LIMIT_KEYS: D.ORG_POLICY_LIMIT_KEYS,
|
|
274
294
|
POOL_CLAIM_FIELDS: D.POOL_CLAIM_FIELDS,
|
|
275
295
|
CREATE_READY_SESSION_REFUSALS: D.CREATE_READY_SESSION_REFUSALS,
|
|
@@ -52,6 +52,16 @@ export interface ScopedRoute {
|
|
|
52
52
|
readonly pre: string;
|
|
53
53
|
readonly post: string;
|
|
54
54
|
}
|
|
55
|
+
/** exec_adopt's body keys, closed (S1 amendment 64): the seal's source_ref and the run's claim nonce. */
|
|
56
|
+
export declare const ADOPT_BODY_KEYS: readonly ["source_ref", "nonce"];
|
|
57
|
+
/** A seal's source_ref is at most this long (the api's batch-seal limit). */
|
|
58
|
+
export declare const ADOPT_SOURCE_REF_MAX = 500;
|
|
59
|
+
/**
|
|
60
|
+
* Whether `v` is exactly {source_ref, nonce}: source_ref a trimmed, non-empty string of at most
|
|
61
|
+
* ADOPT_SOURCE_REF_MAX, and nonce under exec_claim's rule (a non-empty string). The lease adopt
|
|
62
|
+
* creates is fenced on that nonce, so the run holds it with the nonce it sends on every later call.
|
|
63
|
+
*/
|
|
64
|
+
export declare function adoptBodyOk(v: unknown): boolean;
|
|
55
65
|
export declare const ROUTES: Readonly<{
|
|
56
66
|
/** Start an enrollment (device code). */
|
|
57
67
|
exec_enroll: FixedRoute;
|
|
@@ -73,8 +83,20 @@ export declare const ROUTES: Readonly<{
|
|
|
73
83
|
exec_heartbeat: FixedRoute;
|
|
74
84
|
/** The org-scope executor's claimable pool sessions, oldest first; `?limit=` (READY_SESSIONS_LIMIT_MIN..MAX) is not part of the match. */
|
|
75
85
|
exec_ready_list: FixedRoute;
|
|
76
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Body {oidc_token, adopt_ticket?} (doors.ts oidcExchangeBodyOk, Amendment 66). adopt_ticket comes
|
|
88
|
+
* only from a ticketed wake-up; it is used up before any key is minted and binds the key to one seal.
|
|
89
|
+
* Amendment 65: single-use per token jti and per run attempt (token_replayed, run_already_exchanged).
|
|
90
|
+
*/
|
|
77
91
|
exec_oidc_exchange: FixedRoute;
|
|
92
|
+
/**
|
|
93
|
+
* Body {source_ref, nonce} (adoptBodyOk). The OIDC run's key unparks the parked dispatch seal whose
|
|
94
|
+
* source_ref is this run's own issue, in its own org and repo, and claims it in one transaction,
|
|
95
|
+
* and only the seal its adopt ticket was minted for (Amendment 66; a key with no ticket adopts none),
|
|
96
|
+
* fenced on the nonce (exec_claim's rule);
|
|
97
|
+
* answers the exec_claim answer, or adopt_refused / seal_repo_mismatch, changing nothing.
|
|
98
|
+
*/
|
|
99
|
+
exec_adopt: FixedRoute;
|
|
78
100
|
/** Content-free summary {admitted, delivered, oldest_admitted_at} (inboxSummaryResponseOk); marks nothing. */
|
|
79
101
|
exec_inbox: ScopedRoute;
|
|
80
102
|
/** Body {epoch, nonce, limit?}: marks up to limit messages delivered at the live epoch, journals inbox.delivered, answers their delivery text. */
|
|
@@ -42,6 +42,26 @@ import { HEARTBEAT_ENTRY_FIELDS } from "./codes.js";
|
|
|
42
42
|
import { parseContract } from "./envelope.js";
|
|
43
43
|
import { GATEWAY_EVENTS_ROUTE, GATEWAY_WRITABLE_KINDS, validateRefs } from "./events.js";
|
|
44
44
|
import { ALL_STATE_REASONS, isSessionState, isValidStateReason } from "./states.js";
|
|
45
|
+
/** exec_adopt's body keys, closed (S1 amendment 64): the seal's source_ref and the run's claim nonce. */
|
|
46
|
+
export const ADOPT_BODY_KEYS = ["source_ref", "nonce"];
|
|
47
|
+
/** A seal's source_ref is at most this long (the api's batch-seal limit). */
|
|
48
|
+
export const ADOPT_SOURCE_REF_MAX = 500;
|
|
49
|
+
/**
|
|
50
|
+
* Whether `v` is exactly {source_ref, nonce}: source_ref a trimmed, non-empty string of at most
|
|
51
|
+
* ADOPT_SOURCE_REF_MAX, and nonce under exec_claim's rule (a non-empty string). The lease adopt
|
|
52
|
+
* creates is fenced on that nonce, so the run holds it with the nonce it sends on every later call.
|
|
53
|
+
*/
|
|
54
|
+
export function adoptBodyOk(v) {
|
|
55
|
+
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
56
|
+
return false;
|
|
57
|
+
const b = v;
|
|
58
|
+
if (Object.keys(b).length !== 2 || typeof b.source_ref !== "string")
|
|
59
|
+
return false;
|
|
60
|
+
if (typeof b.nonce !== "string" || b.nonce.length === 0)
|
|
61
|
+
return false;
|
|
62
|
+
const r = b.source_ref;
|
|
63
|
+
return r !== "" && r === r.trim() && r.length <= ADOPT_SOURCE_REF_MAX;
|
|
64
|
+
}
|
|
45
65
|
const fixed = (method, auth, path) => Object.freeze({ method, auth, path });
|
|
46
66
|
const scoped = (method, auth, pre, post) => Object.freeze({ method, auth, pre, post });
|
|
47
67
|
const EXECUTORS = "/v1/executors";
|
|
@@ -72,8 +92,21 @@ export const ROUTES = Object.freeze({
|
|
|
72
92
|
// ---- Doors amendment (ruling 34.03) ----
|
|
73
93
|
/** The org-scope executor's claimable pool sessions, oldest first; `?limit=` (READY_SESSIONS_LIMIT_MIN..MAX) is not part of the match. */
|
|
74
94
|
exec_ready_list: fixed("GET", "executor_key", "/v1/executor/ready"),
|
|
75
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Body {oidc_token, adopt_ticket?} (doors.ts oidcExchangeBodyOk, Amendment 66). adopt_ticket comes
|
|
97
|
+
* only from a ticketed wake-up; it is used up before any key is minted and binds the key to one seal.
|
|
98
|
+
* Amendment 65: single-use per token jti and per run attempt (token_replayed, run_already_exchanged).
|
|
99
|
+
*/
|
|
76
100
|
exec_oidc_exchange: fixed("POST", "github_oidc", "/v1/executor/oidc/exchange"),
|
|
101
|
+
// ---- S1 amendment 64 (mnemom-test#1336) ----
|
|
102
|
+
/**
|
|
103
|
+
* Body {source_ref, nonce} (adoptBodyOk). The OIDC run's key unparks the parked dispatch seal whose
|
|
104
|
+
* source_ref is this run's own issue, in its own org and repo, and claims it in one transaction,
|
|
105
|
+
* and only the seal its adopt ticket was minted for (Amendment 66; a key with no ticket adopts none),
|
|
106
|
+
* fenced on the nonce (exec_claim's rule);
|
|
107
|
+
* answers the exec_claim answer, or adopt_refused / seal_repo_mismatch, changing nothing.
|
|
108
|
+
*/
|
|
109
|
+
exec_adopt: fixed("POST", "executor_key", "/v1/executor/adopt"),
|
|
77
110
|
// ---- inbox, executor side (S1 amendment 60) ----
|
|
78
111
|
/** Content-free summary {admitted, delivered, oldest_admitted_at} (inboxSummaryResponseOk); marks nothing. */
|
|
79
112
|
exec_inbox: scoped("GET", "executor_key", EXEC_SESSION_PRE, "/inbox"),
|
|
@@ -114,9 +114,9 @@ export declare function approvalSubjectHash(subject: Readonly<Record<string, unk
|
|
|
114
114
|
*/
|
|
115
115
|
export declare const AGENT_SESSIONS_READY_CHECK: string;
|
|
116
116
|
/**
|
|
117
|
-
* agent_sessions_wait_parked_ck (ruling 34.06 (1), (2)):
|
|
118
|
-
* state 'parked'
|
|
119
|
-
* CHECK lists WAIT_REASONS.
|
|
117
|
+
* agent_sessions_wait_parked_ck (ruling 34.06 (1), (2); amendment 64): every PARKED_ONLY_WAIT_REASONS
|
|
118
|
+
* value is valid only with state 'parked'. wait_ref is set exactly when wait_reason is ci, question
|
|
119
|
+
* or budget; the DISPATCH_PARK_WAIT_REASONS carry none. The wait_reason CHECK lists WAIT_REASONS.
|
|
120
120
|
*/
|
|
121
121
|
export declare const AGENT_SESSIONS_WAIT_PARKED_CHECK: string;
|
|
122
122
|
/** agent_executors.scope CHECK (ruling 34.03 (1)). */
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
import { canonicalJson, sha256Hex, UUID_RE } from "./canonical.js";
|
|
42
42
|
import { CANONICAL_CELL_IDS } from "./cell-id.js";
|
|
43
43
|
import { LEASE_SECONDS_DEFAULT, RESUME_RESET_SECONDS_DEFAULT } from "./constants.js";
|
|
44
|
-
import { PARKED_ONLY_WAIT_REASONS } from "./states.js";
|
|
44
|
+
import { DISPATCH_PARK_WAIT_REASONS, PARKED_ONLY_WAIT_REASONS } from "./states.js";
|
|
45
45
|
export const M1_TABLES = [
|
|
46
46
|
"agent_sessions",
|
|
47
47
|
"durable_lease_keys",
|
|
@@ -430,11 +430,12 @@ export async function approvalSubjectHash(subject) {
|
|
|
430
430
|
export const AGENT_SESSIONS_READY_CHECK = "state <> 'ready' OR (durable_mode = 'locked' AND state_reason IS NULL AND home_executor_id IS NULL" +
|
|
431
431
|
" AND lease_executor_id IS NULL AND lease_expires_at IS NULL AND spawn_id IS NULL AND hello_at IS NULL)";
|
|
432
432
|
/**
|
|
433
|
-
* agent_sessions_wait_parked_ck (ruling 34.06 (1), (2)):
|
|
434
|
-
* state 'parked'
|
|
435
|
-
* CHECK lists WAIT_REASONS.
|
|
433
|
+
* agent_sessions_wait_parked_ck (ruling 34.06 (1), (2); amendment 64): every PARKED_ONLY_WAIT_REASONS
|
|
434
|
+
* value is valid only with state 'parked'. wait_ref is set exactly when wait_reason is ci, question
|
|
435
|
+
* or budget; the DISPATCH_PARK_WAIT_REASONS carry none. The wait_reason CHECK lists WAIT_REASONS.
|
|
436
436
|
*/
|
|
437
437
|
export const AGENT_SESSIONS_WAIT_PARKED_CHECK = "CASE WHEN wait_reason IN ('ci','question','budget') THEN state = 'parked' AND wait_ref IS NOT NULL" +
|
|
438
|
+
" WHEN wait_reason IN ('awaiting_issue_run','repo_arm_unreadable') THEN state = 'parked' AND wait_ref IS NULL" +
|
|
438
439
|
" ELSE wait_ref IS NULL END";
|
|
439
440
|
/** agent_executors.scope CHECK (ruling 34.03 (1)). */
|
|
440
441
|
export const AGENT_EXECUTORS_SCOPE_CHECK = "scope IN ('user','org')";
|
|
@@ -451,6 +452,8 @@ export function readyRowOk(row) {
|
|
|
451
452
|
}
|
|
452
453
|
/** The wait-parked CHECK as a predicate. */
|
|
453
454
|
export function waitParkedRowOk(row) {
|
|
455
|
+
if (row.wait_reason !== null && DISPATCH_PARK_WAIT_REASONS.includes(row.wait_reason))
|
|
456
|
+
return row.state === "parked" && row.wait_ref === null;
|
|
454
457
|
return row.wait_reason !== null &&
|
|
455
458
|
PARKED_ONLY_WAIT_REASONS.includes(row.wait_reason)
|
|
456
459
|
? row.state === "parked" && row.wait_ref !== null
|
|
@@ -31,15 +31,28 @@ export declare const STATE_REASONS: {
|
|
|
31
31
|
export declare const ALL_STATE_REASONS: readonly StateReason[];
|
|
32
32
|
/** M1 writers exist for these suspended reasons; the others are fixture-only (P0a-B / P0b writers). */
|
|
33
33
|
export declare const M1_WRITTEN_SUSPENDED_REASONS: readonly ["budget_reached", "identity_mismatch", "attestation_failed", "respawn_failed", "grant_expired", "approval_expired", "approval_denied"];
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* M1's three wait reasons, then ruling 34.06's ci, question and budget, then amendment 64's two
|
|
36
|
+
* dispatch parks (appended).
|
|
37
|
+
*/
|
|
38
|
+
export declare const WAIT_REASONS: readonly ["approval", "moving", "mnemom_unavailable", "ci", "question", "budget", "awaiting_issue_run", "repo_arm_unreadable"];
|
|
36
39
|
export type WaitReason = (typeof WAIT_REASONS)[number];
|
|
37
40
|
/**
|
|
38
41
|
* The wait reasons valid only with state 'parked' (ruling 34.06 (1), agent_sessions_wait_parked_ck).
|
|
39
42
|
* While wait_reason is one of these, every claim answers ineligible `waiting` (34.06 (3)).
|
|
40
43
|
*/
|
|
41
|
-
export declare const PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget"];
|
|
44
|
+
export declare const PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget", "awaiting_issue_run", "repo_arm_unreadable"];
|
|
42
45
|
export type ParkedOnlyWaitReason = (typeof PARKED_ONLY_WAIT_REASONS)[number];
|
|
46
|
+
/**
|
|
47
|
+
* Amendment 64 (mnemom-test#1336): the parks dispatch writes when it creates a pool session, never an
|
|
48
|
+
* executor. `awaiting_issue_run`: the seal's repo is served-arm, so only the seal's own issue run may
|
|
49
|
+
* adopt it. `repo_arm_unreadable`: the repo's arm could not be read, so the session is held visible
|
|
50
|
+
* instead of going ready. Both are PARKED_ONLY_WAIT_REASONS (state 'parked', claims answer
|
|
51
|
+
* ineligible `waiting`) and carry no wait_ref: they have no WAIT_REF_KEYS entry, so waitRefOk
|
|
52
|
+
* refuses them and the executor park body can never write one.
|
|
53
|
+
*/
|
|
54
|
+
export declare const DISPATCH_PARK_WAIT_REASONS: readonly ["awaiting_issue_run", "repo_arm_unreadable"];
|
|
55
|
+
export type DispatchParkWaitReason = (typeof DISPATCH_PARK_WAIT_REASONS)[number];
|
|
43
56
|
export declare function isSessionState(v: unknown): v is SessionState;
|
|
44
57
|
export declare function isTerminalState(s: SessionState): boolean;
|
|
45
58
|
/** Whether `reason` is allowed with `state` by the closed enum. */
|
|
@@ -92,7 +92,10 @@ export const M1_WRITTEN_SUSPENDED_REASONS = [
|
|
|
92
92
|
"approval_expired",
|
|
93
93
|
"approval_denied",
|
|
94
94
|
];
|
|
95
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* M1's three wait reasons, then ruling 34.06's ci, question and budget, then amendment 64's two
|
|
97
|
+
* dispatch parks (appended).
|
|
98
|
+
*/
|
|
96
99
|
export const WAIT_REASONS = [
|
|
97
100
|
"approval",
|
|
98
101
|
"moving",
|
|
@@ -100,6 +103,8 @@ export const WAIT_REASONS = [
|
|
|
100
103
|
"ci",
|
|
101
104
|
"question",
|
|
102
105
|
"budget",
|
|
106
|
+
"awaiting_issue_run",
|
|
107
|
+
"repo_arm_unreadable",
|
|
103
108
|
];
|
|
104
109
|
/**
|
|
105
110
|
* The wait reasons valid only with state 'parked' (ruling 34.06 (1), agent_sessions_wait_parked_ck).
|
|
@@ -109,6 +114,20 @@ export const PARKED_ONLY_WAIT_REASONS = [
|
|
|
109
114
|
"ci",
|
|
110
115
|
"question",
|
|
111
116
|
"budget",
|
|
117
|
+
"awaiting_issue_run",
|
|
118
|
+
"repo_arm_unreadable",
|
|
119
|
+
];
|
|
120
|
+
/**
|
|
121
|
+
* Amendment 64 (mnemom-test#1336): the parks dispatch writes when it creates a pool session, never an
|
|
122
|
+
* executor. `awaiting_issue_run`: the seal's repo is served-arm, so only the seal's own issue run may
|
|
123
|
+
* adopt it. `repo_arm_unreadable`: the repo's arm could not be read, so the session is held visible
|
|
124
|
+
* instead of going ready. Both are PARKED_ONLY_WAIT_REASONS (state 'parked', claims answer
|
|
125
|
+
* ineligible `waiting`) and carry no wait_ref: they have no WAIT_REF_KEYS entry, so waitRefOk
|
|
126
|
+
* refuses them and the executor park body can never write one.
|
|
127
|
+
*/
|
|
128
|
+
export const DISPATCH_PARK_WAIT_REASONS = [
|
|
129
|
+
"awaiting_issue_run",
|
|
130
|
+
"repo_arm_unreadable",
|
|
112
131
|
];
|
|
113
132
|
export function isSessionState(v) {
|
|
114
133
|
return typeof v === "string" && SESSION_STATES.includes(v);
|