@mnemom/mnemom 0.17.39-next.0 → 0.17.39-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent-run.js +2 -0
- package/dist/commands/auth.d.ts +46 -1
- package/dist/commands/auth.js +51 -2
- package/dist/durable/turn-chain.js +3 -1
- package/dist/host/inbox-delivery.js +3 -1
- package/dist/index.js +3 -2
- package/dist/lib/api.d.ts +19 -0
- package/dist/lib/api.js +8 -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 +168 -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 +88 -4
- package/dist/vendor/shared/durable-contract/doors.js +142 -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 +25 -5
- package/dist/vendor/shared/durable-contract/freeze-record.js +25 -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
|
@@ -33,10 +33,93 @@ 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
|
+
/**
|
|
87
|
+
* Amendment 66, part 2 (Durable lead ruling (a), 2026-11-09): the api env var naming the set of
|
|
88
|
+
* workflow files the exchange accepts, comma-separated bare file names. Unset, the set is
|
|
89
|
+
* OIDC_WORKFLOW_ENV's one file, exactly as before. Set, it replaces that pin for the exchange only
|
|
90
|
+
* (the wake-up still dispatches OIDC_WORKFLOW_ENV's file), and a set that is empty or holds any
|
|
91
|
+
* entry that is not a workflow file name (a wildcard, a "/", a "..") turns the exchange dark.
|
|
92
|
+
*/
|
|
93
|
+
export declare const OIDC_WORKFLOW_SET_ENV = "DURABLE_OIDC_WORKFLOWS";
|
|
94
|
+
/** The most workflow files the set may name. */
|
|
95
|
+
export declare const OIDC_WORKFLOW_SET_MAX = 8;
|
|
96
|
+
/** True when `t` has an adopt ticket's shape. */
|
|
97
|
+
export declare function adoptTicketOk(t: unknown): t is string;
|
|
98
|
+
/**
|
|
99
|
+
* The exchange body: an object with exactly oidc_token (a non-empty string of at most
|
|
100
|
+
* OIDC_TOKEN_MAX) and, optionally, adopt_ticket (a string; its shape is adoptTicketOk's, and a
|
|
101
|
+
* string of the wrong shape is refused adopt_ticket_invalid, not a malformed body).
|
|
102
|
+
*/
|
|
103
|
+
export declare function oidcExchangeBodyOk(v: unknown): boolean;
|
|
104
|
+
/** The pinned workflow file from the env value, or null (then the exchange is dark). */
|
|
105
|
+
export declare function oidcPinnedWorkflow(envValue: string | undefined): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* The workflow files the exchange accepts, or null (then the exchange is dark).
|
|
108
|
+
* `setValue` is OIDC_WORKFLOW_SET_ENV's value, `pinValue` OIDC_WORKFLOW_ENV's. With the set unset
|
|
109
|
+
* the answer is the one pinned file, as oidcPinnedWorkflow reads it. With it set, every
|
|
110
|
+
* comma-separated entry, trimmed, must be a workflow file name with no "..", and there must be one
|
|
111
|
+
* to OIDC_WORKFLOW_SET_MAX of them; anything else is null, never a partial set. Duplicates collapse.
|
|
112
|
+
*/
|
|
113
|
+
export declare function oidcWorkflowSet(setValue: string | undefined, pinValue: string | undefined): readonly string[] | null;
|
|
114
|
+
/**
|
|
115
|
+
* The workflow pin: the token's job_workflow_ref must be exactly
|
|
116
|
+
* `<repository>/.github/workflows/<pinned file>@refs/heads/<default branch>`, with `repository`
|
|
117
|
+
* spelled as the token's own claim. Anything else (another workflow, a reusable workflow, another
|
|
118
|
+
* branch, a tag or sha ref) is refused ref_not_allowed.
|
|
119
|
+
*/
|
|
120
|
+
export declare function jobWorkflowRefOk(jobWorkflowRef: unknown, repository: string, workflowFile: string, defaultBranch: string): boolean;
|
|
121
|
+
/** jobWorkflowRefOk for any one file of an oidcWorkflowSet answer (amendment 66, part 2). */
|
|
122
|
+
export declare function jobWorkflowRefInSet(jobWorkflowRef: unknown, repository: string, workflowFiles: readonly string[], defaultBranch: string): boolean;
|
|
40
123
|
/** durable_org_policy.limits keys (34.05 (1)); live_lease_cap is optional. */
|
|
41
124
|
export declare const ORG_POLICY_LIMIT_KEYS: readonly ["per_session_budget_usd", "live_lease_cap"];
|
|
42
125
|
/** live_lease_cap and the registry default share this range. */
|
|
@@ -61,7 +144,7 @@ export declare function effectiveLeaseCap(orgLimits: {
|
|
|
61
144
|
export declare const DISPATCH_SESSION_CID_PREFIX = "dispatch-";
|
|
62
145
|
export declare const DISPATCH_SESSION_NAME_PREFIX = "job-";
|
|
63
146
|
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"];
|
|
147
|
+
export declare const CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused", "parked"];
|
|
65
148
|
/**
|
|
66
149
|
* Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
|
|
67
150
|
* api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
|
|
@@ -75,6 +158,7 @@ export declare const CREATE_READY_SESSION_ANSWERS: Readonly<{
|
|
|
75
158
|
readonly created: readonly ["outcome", "session_id"];
|
|
76
159
|
readonly existing: readonly ["outcome", "session_id"];
|
|
77
160
|
readonly refused: readonly ["outcome", "reason"];
|
|
161
|
+
readonly parked: readonly ["outcome", "session_id", "wait_reason"];
|
|
78
162
|
}>;
|
|
79
163
|
/** Whether `v` is a closed answer: its outcome's keys only, a lowercase uuid session_id or a listed reason. */
|
|
80
164
|
export declare function createReadySessionAnswerOk(v: unknown): boolean;
|
|
@@ -123,7 +207,7 @@ export declare const POOL_JOB_RETRIES_CONFIG_CAUSE = "pool_job_retries_config";
|
|
|
123
207
|
/** The org's retry count from its limits.pool_job_retries: the default when absent, null when invalid. */
|
|
124
208
|
export declare function poolJobRetriesOf(limit: unknown): number | null;
|
|
125
209
|
/** 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"];
|
|
210
|
+
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
211
|
/** Under --fail-mode open, only these may be ridden through, and only once a verified bundle is held (34.12 (2)). */
|
|
128
212
|
export declare const RUNNER_OPEN_MODE_TOLERATED: readonly ["bundle_unavailable", "job_unavailable", "api_unavailable"];
|
|
129
213
|
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,141 @@ 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
|
+
/**
|
|
117
|
+
* Amendment 66, part 2 (Durable lead ruling (a), 2026-11-09): the api env var naming the set of
|
|
118
|
+
* workflow files the exchange accepts, comma-separated bare file names. Unset, the set is
|
|
119
|
+
* OIDC_WORKFLOW_ENV's one file, exactly as before. Set, it replaces that pin for the exchange only
|
|
120
|
+
* (the wake-up still dispatches OIDC_WORKFLOW_ENV's file), and a set that is empty or holds any
|
|
121
|
+
* entry that is not a workflow file name (a wildcard, a "/", a "..") turns the exchange dark.
|
|
122
|
+
*/
|
|
123
|
+
export const OIDC_WORKFLOW_SET_ENV = "DURABLE_OIDC_WORKFLOWS";
|
|
124
|
+
/** The most workflow files the set may name. */
|
|
125
|
+
export const OIDC_WORKFLOW_SET_MAX = 8;
|
|
126
|
+
/** True when `t` has an adopt ticket's shape. */
|
|
127
|
+
export function adoptTicketOk(t) {
|
|
128
|
+
return typeof t === "string" && ADOPT_TICKET_RE.test(t);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The exchange body: an object with exactly oidc_token (a non-empty string of at most
|
|
132
|
+
* OIDC_TOKEN_MAX) and, optionally, adopt_ticket (a string; its shape is adoptTicketOk's, and a
|
|
133
|
+
* string of the wrong shape is refused adopt_ticket_invalid, not a malformed body).
|
|
134
|
+
*/
|
|
135
|
+
export function oidcExchangeBodyOk(v) {
|
|
136
|
+
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
137
|
+
return false;
|
|
138
|
+
const o = v;
|
|
139
|
+
const keys = Object.keys(o);
|
|
140
|
+
if (keys.some((k) => !OIDC_EXCHANGE_BODY_KEYS.includes(k)))
|
|
141
|
+
return false;
|
|
142
|
+
if (typeof o.oidc_token !== "string" || o.oidc_token === "" || o.oidc_token.length > OIDC_TOKEN_MAX)
|
|
143
|
+
return false;
|
|
144
|
+
return !("adopt_ticket" in o) || typeof o.adopt_ticket === "string";
|
|
145
|
+
}
|
|
146
|
+
/** The pinned workflow file from the env value, or null (then the exchange is dark). */
|
|
147
|
+
export function oidcPinnedWorkflow(envValue) {
|
|
148
|
+
const wf = envValue?.trim() ?? "";
|
|
149
|
+
return OIDC_WORKFLOW_FILE_RE.test(wf) ? wf : null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The workflow files the exchange accepts, or null (then the exchange is dark).
|
|
153
|
+
* `setValue` is OIDC_WORKFLOW_SET_ENV's value, `pinValue` OIDC_WORKFLOW_ENV's. With the set unset
|
|
154
|
+
* the answer is the one pinned file, as oidcPinnedWorkflow reads it. With it set, every
|
|
155
|
+
* comma-separated entry, trimmed, must be a workflow file name with no "..", and there must be one
|
|
156
|
+
* to OIDC_WORKFLOW_SET_MAX of them; anything else is null, never a partial set. Duplicates collapse.
|
|
157
|
+
*/
|
|
158
|
+
export function oidcWorkflowSet(setValue, pinValue) {
|
|
159
|
+
if (setValue === undefined) {
|
|
160
|
+
const pin = oidcPinnedWorkflow(pinValue);
|
|
161
|
+
return pin === null ? null : [pin];
|
|
162
|
+
}
|
|
163
|
+
const names = setValue.split(",").map((n) => n.trim());
|
|
164
|
+
if (names.some((n) => !OIDC_WORKFLOW_FILE_RE.test(n) || n.includes("..")))
|
|
165
|
+
return null;
|
|
166
|
+
const set = [...new Set(names)];
|
|
167
|
+
return set.length > OIDC_WORKFLOW_SET_MAX ? null : set;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The workflow pin: the token's job_workflow_ref must be exactly
|
|
171
|
+
* `<repository>/.github/workflows/<pinned file>@refs/heads/<default branch>`, with `repository`
|
|
172
|
+
* spelled as the token's own claim. Anything else (another workflow, a reusable workflow, another
|
|
173
|
+
* branch, a tag or sha ref) is refused ref_not_allowed.
|
|
174
|
+
*/
|
|
175
|
+
export function jobWorkflowRefOk(jobWorkflowRef, repository, workflowFile, defaultBranch) {
|
|
176
|
+
return (typeof jobWorkflowRef === "string" &&
|
|
177
|
+
jobWorkflowRef === `${repository}/.github/workflows/${workflowFile}@refs/heads/${defaultBranch}`);
|
|
178
|
+
}
|
|
179
|
+
/** jobWorkflowRefOk for any one file of an oidcWorkflowSet answer (amendment 66, part 2). */
|
|
180
|
+
export function jobWorkflowRefInSet(jobWorkflowRef, repository, workflowFiles, defaultBranch) {
|
|
181
|
+
return workflowFiles.some((f) => jobWorkflowRefOk(jobWorkflowRef, repository, f, defaultBranch));
|
|
182
|
+
}
|
|
56
183
|
// ---- Per-org live-lease cap (rulings 34.05, 34.05.1) ----------------------------------------
|
|
57
184
|
/** durable_org_policy.limits keys (34.05 (1)); live_lease_cap is optional. */
|
|
58
185
|
export const ORG_POLICY_LIMIT_KEYS = ["per_session_budget_usd", "live_lease_cap"];
|
|
@@ -99,7 +226,9 @@ export const CREATE_READY_SESSION_REFUSALS = [
|
|
|
99
226
|
// 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
|
|
100
227
|
"charter_mismatch",
|
|
101
228
|
];
|
|
102
|
-
|
|
229
|
+
// S1 amendment 64 (mnemom-test#1336): "parked", the seal's repo does not take it through the pool;
|
|
230
|
+
// counted apart from created, and the wake-up never sends for it
|
|
231
|
+
export const CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused", "parked"];
|
|
103
232
|
/**
|
|
104
233
|
* Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
|
|
105
234
|
* api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
|
|
@@ -115,6 +244,7 @@ export const CREATE_READY_SESSION_ANSWERS = Object.freeze({
|
|
|
115
244
|
created: ["outcome", "session_id"],
|
|
116
245
|
existing: ["outcome", "session_id"],
|
|
117
246
|
refused: ["outcome", "reason"],
|
|
247
|
+
parked: ["outcome", "session_id", "wait_reason"],
|
|
118
248
|
});
|
|
119
249
|
/** Whether `v` is a closed answer: its outcome's keys only, a lowercase uuid session_id or a listed reason. */
|
|
120
250
|
export function createReadySessionAnswerOk(v) {
|
|
@@ -127,9 +257,12 @@ export function createReadySessionAnswerOk(v) {
|
|
|
127
257
|
const keys = CREATE_READY_SESSION_ANSWERS[a.outcome];
|
|
128
258
|
if (Object.keys(a).length !== keys.length || !keys.every((k) => k in a))
|
|
129
259
|
return false;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
260
|
+
if (a.outcome === "refused")
|
|
261
|
+
return CREATE_READY_SESSION_REFUSALS.includes(a.reason);
|
|
262
|
+
if (a.outcome === "parked" &&
|
|
263
|
+
!DISPATCH_PARK_WAIT_REASONS.includes(a.wait_reason))
|
|
264
|
+
return false;
|
|
265
|
+
return typeof a.session_id === "string" && UUID_RE.test(a.session_id);
|
|
133
266
|
}
|
|
134
267
|
function sealIdOk(sealId) {
|
|
135
268
|
if (!UUID_RE.test(sealId))
|
|
@@ -238,6 +371,10 @@ export const RUNNER_REFUSED_REASONS = [
|
|
|
238
371
|
"oidc_pool_flag_off",
|
|
239
372
|
"oidc_unavailable",
|
|
240
373
|
"api_unavailable",
|
|
374
|
+
"oidc_adopt_ticket_invalid",
|
|
375
|
+
// amendment 65 (mnemom-test#1331): the two single-use exchange refusals, appended
|
|
376
|
+
"oidc_token_replayed",
|
|
377
|
+
"oidc_run_already_exchanged",
|
|
241
378
|
];
|
|
242
379
|
/** Under --fail-mode open, only these may be ridden through, and only once a verified bundle is held (34.12 (2)). */
|
|
243
380
|
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,21 @@ 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;
|
|
131
|
+
OIDC_WORKFLOW_SET_ENV: "DURABLE_OIDC_WORKFLOWS";
|
|
132
|
+
OIDC_WORKFLOW_SET_MAX: 8;
|
|
124
133
|
READY_SESSIONS_LIMIT_MIN: 1;
|
|
125
134
|
READY_SESSIONS_LIMIT_MAX: 50;
|
|
135
|
+
ADOPT_SOURCE_REF_MAX: 500;
|
|
136
|
+
OIDC_JTI_MAX: 200;
|
|
137
|
+
OIDC_RUN_NUMBER_PATTERN: string;
|
|
138
|
+
OIDC_EXCHANGE_RETENTION_DAYS: 35;
|
|
126
139
|
READY_USER_TEXT: "waiting for a runner";
|
|
127
140
|
RUNNER_FAILED_PHASE_PATTERN: string;
|
|
128
141
|
AGENT_SESSIONS_READY_CHECK: string;
|
|
@@ -330,6 +343,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
330
343
|
exec_heartbeat: import("./routes.js").FixedRoute;
|
|
331
344
|
exec_ready_list: import("./routes.js").FixedRoute;
|
|
332
345
|
exec_oidc_exchange: import("./routes.js").FixedRoute;
|
|
346
|
+
exec_adopt: import("./routes.js").FixedRoute;
|
|
333
347
|
exec_inbox: import("./routes.js").ScopedRoute;
|
|
334
348
|
exec_inbox_take: import("./routes.js").ScopedRoute;
|
|
335
349
|
exec_inbox_ack: import("./routes.js").ScopedRoute;
|
|
@@ -724,8 +738,11 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
724
738
|
}>;
|
|
725
739
|
GATEWAY_EVENTS_BODY_KEYS: readonly ["session_id", "kind", "epoch", "idempotency_key", "refs"];
|
|
726
740
|
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"];
|
|
741
|
+
WAIT_REASONS: readonly ["approval", "moving", "mnemom_unavailable", "ci", "question", "budget", "awaiting_issue_run", "repo_arm_unreadable"];
|
|
742
|
+
PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget", "awaiting_issue_run", "repo_arm_unreadable"];
|
|
743
|
+
DISPATCH_PARK_WAIT_REASONS: readonly ["awaiting_issue_run", "repo_arm_unreadable"];
|
|
744
|
+
ADOPT_REFUSED_CAUSES: readonly ["no_parked_seal", "not_parked"];
|
|
745
|
+
ADOPT_BODY_KEYS: readonly ["source_ref", "nonce"];
|
|
729
746
|
WAIT_REF_KEYS: Readonly<{
|
|
730
747
|
readonly ci: readonly ["repo_id", "head_sha"];
|
|
731
748
|
readonly question: readonly ["approval_id"];
|
|
@@ -734,15 +751,18 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
734
751
|
CLEAR_WAIT_OUTCOMES: readonly ["cleared", "stale_ref", "already_clear"];
|
|
735
752
|
EXECUTOR_SCOPES: readonly ["user", "org"];
|
|
736
753
|
ORG_SCOPE_EXECUTOR_KINDS: readonly ["container", "fleet"];
|
|
737
|
-
OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off"];
|
|
754
|
+
OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off", "adopt_ticket_invalid", "token_replayed", "run_already_exchanged"];
|
|
755
|
+
OIDC_EXCHANGE_BODY_KEYS: readonly ["oidc_token", "adopt_ticket"];
|
|
756
|
+
OIDC_EXCHANGE_RUN_CLAIMS: readonly ["jti", "run_id", "run_attempt"];
|
|
738
757
|
ORG_POLICY_LIMIT_KEYS: readonly ["per_session_budget_usd", "live_lease_cap"];
|
|
739
758
|
POOL_CLAIM_FIELDS: readonly ["contract_sha256", "contract_version", "cap"];
|
|
740
759
|
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"];
|
|
760
|
+
CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused", "parked"];
|
|
742
761
|
CREATE_READY_SESSION_ANSWERS: Readonly<{
|
|
743
762
|
readonly created: readonly ["outcome", "session_id"];
|
|
744
763
|
readonly existing: readonly ["outcome", "session_id"];
|
|
745
764
|
readonly refused: readonly ["outcome", "reason"];
|
|
765
|
+
readonly parked: readonly ["outcome", "session_id", "wait_reason"];
|
|
746
766
|
}>;
|
|
747
767
|
JOURNAL_APPROVAL_KINDS: readonly ["approval.resolved", "approval.consumed"];
|
|
748
768
|
RUNNER_OUTCOMES: readonly ["done", "no_job", "failed", "refused"];
|
|
@@ -755,7 +775,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
755
775
|
readonly refused: 3;
|
|
756
776
|
}>;
|
|
757
777
|
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"];
|
|
778
|
+
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
779
|
RUNNER_OPEN_MODE_TOLERATED: readonly ["bundle_unavailable", "job_unavailable", "api_unavailable"];
|
|
760
780
|
RUNNER_MERGE_STRATEGIES: readonly ["external", "merge_on_green"];
|
|
761
781
|
}>;
|
|
@@ -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,24 @@ 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,
|
|
162
|
+
OIDC_WORKFLOW_SET_ENV: D.OIDC_WORKFLOW_SET_ENV,
|
|
163
|
+
OIDC_WORKFLOW_SET_MAX: D.OIDC_WORKFLOW_SET_MAX,
|
|
154
164
|
READY_SESSIONS_LIMIT_MIN: D.READY_SESSIONS_LIMIT_MIN,
|
|
155
165
|
READY_SESSIONS_LIMIT_MAX: D.READY_SESSIONS_LIMIT_MAX,
|
|
166
|
+
// amendment 64
|
|
167
|
+
ADOPT_SOURCE_REF_MAX,
|
|
168
|
+
// amendment 65
|
|
169
|
+
OIDC_JTI_MAX: D.OIDC_JTI_MAX,
|
|
170
|
+
OIDC_RUN_NUMBER_PATTERN: D.OIDC_RUN_NUMBER_RE.source,
|
|
171
|
+
OIDC_EXCHANGE_RETENTION_DAYS: D.OIDC_EXCHANGE_RETENTION_DAYS,
|
|
156
172
|
READY_USER_TEXT,
|
|
157
173
|
RUNNER_FAILED_PHASE_PATTERN: D.RUNNER_FAILED_PHASE_RE.source,
|
|
158
174
|
AGENT_SESSIONS_READY_CHECK,
|
|
@@ -265,11 +281,17 @@ export const FREEZE_STRUCTURED = Object.freeze({
|
|
|
265
281
|
READY_CAUSES,
|
|
266
282
|
WAIT_REASONS,
|
|
267
283
|
PARKED_ONLY_WAIT_REASONS,
|
|
284
|
+
// amendment 64
|
|
285
|
+
DISPATCH_PARK_WAIT_REASONS,
|
|
286
|
+
ADOPT_REFUSED_CAUSES,
|
|
287
|
+
ADOPT_BODY_KEYS,
|
|
268
288
|
WAIT_REF_KEYS: D.WAIT_REF_KEYS,
|
|
269
289
|
CLEAR_WAIT_OUTCOMES: D.CLEAR_WAIT_OUTCOMES,
|
|
270
290
|
EXECUTOR_SCOPES: D.EXECUTOR_SCOPES,
|
|
271
291
|
ORG_SCOPE_EXECUTOR_KINDS: D.ORG_SCOPE_EXECUTOR_KINDS,
|
|
272
292
|
OIDC_EXCHANGE_REFUSALS: D.OIDC_EXCHANGE_REFUSALS,
|
|
293
|
+
OIDC_EXCHANGE_BODY_KEYS: D.OIDC_EXCHANGE_BODY_KEYS,
|
|
294
|
+
OIDC_EXCHANGE_RUN_CLAIMS: D.OIDC_EXCHANGE_RUN_CLAIMS,
|
|
273
295
|
ORG_POLICY_LIMIT_KEYS: D.ORG_POLICY_LIMIT_KEYS,
|
|
274
296
|
POOL_CLAIM_FIELDS: D.POOL_CLAIM_FIELDS,
|
|
275
297
|
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)). */
|