@mnemom/mnemom 0.17.38-next.2 → 0.17.38

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.
@@ -92,7 +92,12 @@ export async function agentRunCommand(flags, deps) {
92
92
  executorId = executorIdFromState(await deps.readExecutorState(), executorKey) ?? executorId;
93
93
  }
94
94
  const api = runnerApi({ apiUrl: deps.apiUrl, executorKey, fetch: deps.fetch });
95
- const loop = { failMode: plan.failMode, stop: deps.stop, emit: deps.emit };
95
+ const loop = {
96
+ failMode: plan.failMode,
97
+ stop: deps.stop,
98
+ emit: deps.emit,
99
+ ...(plan.sessionId !== undefined ? { sessionId: plan.sessionId } : {}),
100
+ };
96
101
  const job = deps.jobDeps(api, executorId, gh.bases, modelKey, plan.credential.kind === "oidc");
97
102
  return plan.form === "once" ? runOnce(job, loop) : runWatch(job, loop);
98
103
  }
package/dist/index.js CHANGED
@@ -1220,6 +1220,7 @@ agentCmd
1220
1220
  .option("--fail-mode <mode>", "closed (default) or open; also MNEMOM_RUNNER_FAIL_MODE")
1221
1221
  .option("--json", "Print only outcome lines on stdout; logs go to stderr")
1222
1222
  .option("--oidc", "With --once: exchange the GitHub Actions ID token for a one-job key")
1223
+ .option("--session <uuid>", "With --once: claim only this session (a wake-up's own); never take another from the pool")
1223
1224
  .hook("preAction", () => requireExperimental("`mnemom agent run`", "dispatch"))
1224
1225
  .action(async (opts) => {
1225
1226
  process.exitCode = await runAgentRun(opts);
@@ -3,8 +3,11 @@
3
3
  *
4
4
  * --once: list the ready pool sessions (exec_ready_list, oldest first), try each in order, and run
5
5
  * at most one job. A lost race, or an ineligible waiting / not_pool_member, moves to the next
6
- * candidate; an empty list, or every candidate lost, ends no_job. --watch: the same, again and
7
- * again, until SIGTERM or SIGINT. Every job prints exactly one outcome line (none when stopped
6
+ * candidate; an empty list, or every candidate lost, ends no_job. --once --session <id> (the
7
+ * wake-up's own session, #1329): try exactly that session and never list the pool; a 404 attest, a
8
+ * lost race or a skippable ineligible ends no_job, and no other session is tried, so a wake-up
9
+ * dispatched for one repo's session never works another's. --watch: the same as plain --once, again
10
+ * and again, until SIGTERM or SIGINT. Every job prints exactly one outcome line (none when stopped
8
11
  * mid-job), and the process exit code is the --once outcome's, or 0 for --watch stopped.
9
12
  *
10
13
  * Every side effect is in `JobDeps` (plus `emit`), so each arm is tested; `liveJobDeps`
@@ -21,6 +24,8 @@ export interface LoopOptions {
21
24
  emit(line: string): void;
22
25
  /** --watch: wait between empty polls, ms. */
23
26
  idlePollMs?: number;
27
+ /** --once --session: the one session to try; the ready list is never read. */
28
+ sessionId?: string;
24
29
  }
25
30
  export declare const WATCH_IDLE_POLL_MS = 15000;
26
31
  /** --watch backs off on a ready-list the api cannot answer, up to this, ms. */
@@ -3,8 +3,11 @@
3
3
  *
4
4
  * --once: list the ready pool sessions (exec_ready_list, oldest first), try each in order, and run
5
5
  * at most one job. A lost race, or an ineligible waiting / not_pool_member, moves to the next
6
- * candidate; an empty list, or every candidate lost, ends no_job. --watch: the same, again and
7
- * again, until SIGTERM or SIGINT. Every job prints exactly one outcome line (none when stopped
6
+ * candidate; an empty list, or every candidate lost, ends no_job. --once --session <id> (the
7
+ * wake-up's own session, #1329): try exactly that session and never list the pool; a 404 attest, a
8
+ * lost race or a skippable ineligible ends no_job, and no other session is tried, so a wake-up
9
+ * dispatched for one repo's session never works another's. --watch: the same as plain --once, again
10
+ * and again, until SIGTERM or SIGINT. Every job prints exactly one outcome line (none when stopped
8
11
  * mid-job), and the process exit code is the --once outcome's, or 0 for --watch stopped.
9
12
  *
10
13
  * Every side effect is in `JobDeps` (plus `emit`), so each arm is tested; `liveJobDeps`
@@ -58,9 +61,18 @@ async function onePass(deps, opts) {
58
61
  }
59
62
  return { kind: "none" };
60
63
  }
64
+ /** --once --session: that session's outcome, or none; never another session. */
65
+ async function sessionPass(deps, opts, id) {
66
+ if (opts.stop.aborted)
67
+ return { kind: "stopped" };
68
+ const r = await runJob(id, { failMode: opts.failMode, stop: opts.stop }, deps);
69
+ return r.kind === "next" ? { kind: "none" } : r;
70
+ }
61
71
  /** --once: at most one job. Resolves the process exit code. */
62
72
  export async function runOnce(deps, opts) {
63
- const p = await onePass(deps, opts);
73
+ const p = opts.sessionId === undefined
74
+ ? await onePass(deps, opts)
75
+ : await sessionPass(deps, opts, opts.sessionId);
64
76
  if (p.kind === "stopped")
65
77
  return 0;
66
78
  const outcome = p.kind === "outcome" ? p.outcome : noJobOutcome();
@@ -16,6 +16,8 @@ export interface RunFlags {
16
16
  failMode?: string;
17
17
  json?: boolean;
18
18
  oidc?: boolean;
19
+ /** --session <uuid>: with --once, claim exactly this session and never list the pool. */
20
+ session?: string;
19
21
  }
20
22
  export type RunCredential = {
21
23
  kind: "executor_key";
@@ -34,6 +36,11 @@ export interface RunPlan {
34
36
  executorId: string | null;
35
37
  /** Warnings to print on stderr (an unknown fail mode reads as closed). */
36
38
  warnings: string[];
39
+ /**
40
+ * --session: the one session this --once run may claim (the wake-up's own, #1329). Absent: the
41
+ * pool's ready list, oldest first, as before.
42
+ */
43
+ sessionId?: string;
37
44
  }
38
45
  export type RunPlanResult = {
39
46
  ok: true;
@@ -22,6 +22,16 @@ export function resolveRunPlan(flags, env) {
22
22
  if (flags.oidc && form === "watch") {
23
23
  return usage("mnemom agent run: --oidc is valid only with --once");
24
24
  }
25
+ let sessionId;
26
+ if (flags.session !== undefined) {
27
+ if (form === "watch") {
28
+ return usage("mnemom agent run: --session is valid only with --once");
29
+ }
30
+ if (!UUID_RE.test(flags.session)) {
31
+ return usage("mnemom agent run: --session is not a lowercase uuid");
32
+ }
33
+ sessionId = flags.session;
34
+ }
25
35
  const fail = resolveFailMode(flags.failMode, env[RUNNER_FAIL_MODE_ENV]);
26
36
  const warnings = fail.warning ? [`mnemom agent run: ${fail.warning}`] : [];
27
37
  let credential;
@@ -60,6 +70,7 @@ export function resolveRunPlan(flags, env) {
60
70
  credential,
61
71
  executorId,
62
72
  warnings,
73
+ ...(sessionId !== undefined ? { sessionId } : {}),
63
74
  },
64
75
  };
65
76
  }
@@ -1065,6 +1065,7 @@ var CLAIM_UNAVAILABLE_CAUSES = [
1065
1065
  /** A pool claim with no valid registry default cap (ruling 34.05.1 (3)). */
1066
1066
  "lease_cap_config"
1067
1067
  ];
1068
+ var SEAL_REPO_MISMATCH_CAUSES = ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
1068
1069
  var UNSUPPORTED_FEATURES = [
1069
1070
  "tool_classes",
1070
1071
  "launch_spec_change",
@@ -1173,6 +1174,11 @@ var DURABLE_CODES = {
1173
1174
  // The per-org claim rate limit (ruling 34.24): nothing written, no key minted. retry_after_s is
1174
1175
  // the number of seconds to wait (claimRateLimitedDetailsOk).
1175
1176
  durable_claim_rate_limited: { status: 429, surfaces: ["api"], fields: ["retry_after_s"] },
1177
+ // An OIDC executor key (bound to the repo whose GitHub Actions token minted it) claiming a session
1178
+ // not sealed for that repo (mnemom-test#1329; mnemom-api#4530). Its own code, not a
1179
+ // durable_claim_ineligible reason: the session is claimable, only not by this executor, and a
1180
+ // runner never skips past it to another session. cause is a SEAL_REPO_MISMATCH_CAUSES value.
1181
+ seal_repo_mismatch: { status: 403, surfaces: ["api"], fields: ["cause"] },
1176
1182
  // ---- api journal (C10-R0 (i), (j), (k)) ----
1177
1183
  /** Lost CAS race on (session_id, seq). */
1178
1184
  durable_append_conflict: { status: 409, surfaces: ["api"], fields: ["tip_seq"] },
@@ -1670,7 +1676,8 @@ var M1_CLI_SURFACE = Object.freeze([
1670
1676
  entry(["agent", "pause"], ["--json"]),
1671
1677
  entry(["agent", "resume"], ["--json"]),
1672
1678
  // Doors thin runner (W-L13), ruling 34.12 (1): --watch or --once, --fail-mode, --json, --oidc.
1673
- entry(["agent", "run"], ["--fail-mode", "--json", "--oidc", "--once", "--watch"]),
1679
+ // --session (#1329, approved by the Durable lead): with --once, claim only the wake-up's session.
1680
+ entry(["agent", "run"], ["--fail-mode", "--json", "--oidc", "--once", "--session", "--watch"]),
1674
1681
  // BACKBONE C4 (line 147: buildDurableLaunchSpec serves 'sessions adopt'); options: C14 spec
1675
1682
  // (`sessions adopt --dry-run --json`, `sessions adopt --own-agent`); ruling 32.33.
1676
1683
  entry(["agent", "sessions", "adopt"], ["--dry-run", "--json", "--own-agent"]),
@@ -2393,12 +2400,18 @@ var DISPATCH_SESSION_CID_PREFIX = "dispatch-";
2393
2400
  var DISPATCH_SESSION_NAME_PREFIX = "job-";
2394
2401
  var CREATE_READY_SESSION_REFUSALS = [
2395
2402
  "no_dispatch_agent",
2403
+ // amendment 63 (signed off by Wassim 2026-11-05): neither the seal's confirmer nor the dispatch agent's
2404
+ // creator is a user (NULL, empty or scheme-prefixed, such as github:<login>); nothing written
2405
+ "dispatch_owner_missing",
2396
2406
  "seal_not_found",
2397
2407
  "cell_unmapped",
2398
2408
  // 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
2399
2409
  "charter_mismatch"
2400
2410
  ];
2401
2411
  var CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
2412
+ function isUserCreator(createdBy) {
2413
+ return typeof createdBy === "string" && createdBy !== "" && !createdBy.includes(":");
2414
+ }
2402
2415
  var CREATE_READY_SESSION_ANSWERS = Object.freeze({
2403
2416
  created: ["outcome", "session_id"],
2404
2417
  existing: ["outcome", "session_id"],
@@ -4092,7 +4105,9 @@ var M1_COLUMNS = {
4092
4105
  "last_seen_at",
4093
4106
  "risk_line_version",
4094
4107
  "created_at",
4095
- "scope"
4108
+ "scope",
4109
+ "oidc_repo",
4110
+ "oidc_bound"
4096
4111
  ],
4097
4112
  agent_durable_profiles: [
4098
4113
  "id",
@@ -6572,6 +6587,7 @@ export {
6572
6587
  RUNNER_OUTCOME_FIELDS,
6573
6588
  RUNNER_REFUSED_REASONS,
6574
6589
  SEALED_CONTRACT_FIELDS,
6590
+ SEAL_REPO_MISMATCH_CAUSES,
6575
6591
  SEAL_VECTORS,
6576
6592
  SEAT_DAEMON_FLAGS,
6577
6593
  SEAT_MAX_ATTEMPTS_NONRETRY,
@@ -6776,6 +6792,7 @@ export {
6776
6792
  isTerminalState,
6777
6793
  isTestAllowlisted,
6778
6794
  isUnchainedCountTokens,
6795
+ isUserCreator,
6779
6796
  isValidCommitment,
6780
6797
  isValidSessionName,
6781
6798
  isValidStateReason,
@@ -60,7 +60,8 @@ export const M1_CLI_SURFACE = Object.freeze([
60
60
  entry(["agent", "pause"], ["--json"]),
61
61
  entry(["agent", "resume"], ["--json"]),
62
62
  // Doors thin runner (W-L13), ruling 34.12 (1): --watch or --once, --fail-mode, --json, --oidc.
63
- entry(["agent", "run"], ["--fail-mode", "--json", "--oidc", "--once", "--watch"]),
63
+ // --session (#1329, approved by the Durable lead): with --once, claim only the wake-up's session.
64
+ entry(["agent", "run"], ["--fail-mode", "--json", "--oidc", "--once", "--session", "--watch"]),
64
65
  // BACKBONE C4 (line 147: buildDurableLaunchSpec serves 'sessions adopt'); options: C14 spec
65
66
  // (`sessions adopt --dry-run --json`, `sessions adopt --own-agent`); ruling 32.33.
66
67
  entry(["agent", "sessions", "adopt"], ["--dry-run", "--json", "--own-agent"]),
@@ -8,6 +8,13 @@
8
8
  */
9
9
  export declare const CLAIM_UNAVAILABLE_CAUSES: readonly ["schema", "arm_probe", "entitlement_lookup", "protection_probe", "guard", "lease_cap_config"];
10
10
  export type ClaimUnavailableCause = (typeof CLAIM_UNAVAILABLE_CAUSES)[number];
11
+ /**
12
+ * seal_repo_mismatch details.cause (mnemom-test#1329; mnemom-api#4530 claim.ts oidcRepoBinding): the
13
+ * bound executor names no repo, the session has no dispatch seal naming a repo, or the seal names
14
+ * another repo than the executor's (owner/name, case-insensitive).
15
+ */
16
+ export declare const SEAL_REPO_MISMATCH_CAUSES: readonly ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
17
+ export type SealRepoMismatchCause = (typeof SEAL_REPO_MISMATCH_CAUSES)[number];
11
18
  export declare const UNSUPPORTED_FEATURES: readonly ["tool_classes", "launch_spec_change", "above_limit_raise", "remote_control"];
12
19
  export type UnsupportedFeature = (typeof UNSUPPORTED_FEATURES)[number];
13
20
  export declare const CWD_KINDS: readonly ["subdirectory", "not_git", "symlink_transcript"];
@@ -261,6 +268,11 @@ export declare const DURABLE_CODES: {
261
268
  readonly surfaces: readonly ["api"];
262
269
  readonly fields: readonly ["retry_after_s"];
263
270
  };
271
+ readonly seal_repo_mismatch: {
272
+ readonly status: 403;
273
+ readonly surfaces: readonly ["api"];
274
+ readonly fields: readonly ["cause"];
275
+ };
264
276
  /** Lost CAS race on (session_id, seq). */
265
277
  readonly durable_append_conflict: {
266
278
  readonly status: 409;
@@ -18,6 +18,12 @@ export const CLAIM_UNAVAILABLE_CAUSES = [
18
18
  /** A pool claim with no valid registry default cap (ruling 34.05.1 (3)). */
19
19
  "lease_cap_config",
20
20
  ];
21
+ /**
22
+ * seal_repo_mismatch details.cause (mnemom-test#1329; mnemom-api#4530 claim.ts oidcRepoBinding): the
23
+ * bound executor names no repo, the session has no dispatch seal naming a repo, or the seal names
24
+ * another repo than the executor's (owner/name, case-insensitive).
25
+ */
26
+ export const SEAL_REPO_MISMATCH_CAUSES = ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
21
27
  export const UNSUPPORTED_FEATURES = [
22
28
  "tool_classes",
23
29
  "launch_spec_change",
@@ -134,6 +140,11 @@ export const DURABLE_CODES = {
134
140
  // The per-org claim rate limit (ruling 34.24): nothing written, no key minted. retry_after_s is
135
141
  // the number of seconds to wait (claimRateLimitedDetailsOk).
136
142
  durable_claim_rate_limited: { status: 429, surfaces: ["api"], fields: ["retry_after_s"] },
143
+ // An OIDC executor key (bound to the repo whose GitHub Actions token minted it) claiming a session
144
+ // not sealed for that repo (mnemom-test#1329; mnemom-api#4530). Its own code, not a
145
+ // durable_claim_ineligible reason: the session is claimable, only not by this executor, and a
146
+ // runner never skips past it to another session. cause is a SEAL_REPO_MISMATCH_CAUSES value.
147
+ seal_repo_mismatch: { status: 403, surfaces: ["api"], fields: ["cause"] },
137
148
  // ---- api journal (C10-R0 (i), (j), (k)) ----
138
149
  /** Lost CAS race on (session_id, seq). */
139
150
  durable_append_conflict: { status: 409, surfaces: ["api"], fields: ["tip_seq"] },
@@ -4,7 +4,13 @@
4
4
  * freeze record" are chosen here; each carries its reason. `freeze-record.ts` copies them into
5
5
  * the JSON record the eval runner reads as `$S1.<NAME>`.
6
6
  */
7
- /** The contract version of this package. Any change to FREEZE.json bumps it. */
7
+ /**
8
+ * The contract version of this package. The per-file hashes in FREEZE.json are the change signal
9
+ * for S1; this number moves only on a breaking change to the contract's shape, by ruling.
10
+ * Precedent: amendments 59, 60 and 34.03.4 all changed FREEZE.json at 1.0.0 (lead ruling 34.35.1,
11
+ * 2026-11-05: the contract version is held at 1.0.0 while FREEZE hashes carry change; a bump is a
12
+ * separate ruling).
13
+ */
8
14
  export declare const DURABLE_CONTRACT_VERSION = "1.0.0";
9
15
  /**
10
16
  * `durable_schema_version()` must return exactly this. A mismatch makes the entitlement `durable`
@@ -5,7 +5,13 @@
5
5
  * freeze record" are chosen here; each carries its reason. `freeze-record.ts` copies them into
6
6
  * the JSON record the eval runner reads as `$S1.<NAME>`.
7
7
  */
8
- /** The contract version of this package. Any change to FREEZE.json bumps it. */
8
+ /**
9
+ * The contract version of this package. The per-file hashes in FREEZE.json are the change signal
10
+ * for S1; this number moves only on a breaking change to the contract's shape, by ruling.
11
+ * Precedent: amendments 59, 60 and 34.03.4 all changed FREEZE.json at 1.0.0 (lead ruling 34.35.1,
12
+ * 2026-11-05: the contract version is held at 1.0.0 while FREEZE hashes carry change; a bump is a
13
+ * separate ruling).
14
+ */
9
15
  export const DURABLE_CONTRACT_VERSION = "1.0.0";
10
16
  /**
11
17
  * `durable_schema_version()` must return exactly this. A mismatch makes the entitlement `durable`
@@ -60,8 +60,16 @@ export declare function effectiveLeaseCap(orgLimits: {
60
60
  } | null, defaultCap: number | null): number;
61
61
  export declare const DISPATCH_SESSION_CID_PREFIX = "dispatch-";
62
62
  export declare const DISPATCH_SESSION_NAME_PREFIX = "job-";
63
- export declare const CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "seal_not_found", "cell_unmapped", "charter_mismatch"];
63
+ export declare const CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "dispatch_owner_missing", "seal_not_found", "cell_unmapped", "charter_mismatch"];
64
64
  export declare const CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused"];
65
+ /**
66
+ * Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
67
+ * api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
68
+ * public.durable_is_user_creator. A creator is a user unless it is null, empty or scheme-prefixed
69
+ * (contains ':', as github:<login> does). No id shape is read: uuids, Zitadel numeric ids and
70
+ * test-mode harness-<x> are users, so an identity-provider change does not move it.
71
+ */
72
+ export declare function isUserCreator(createdBy: unknown): boolean;
65
73
  /** durable_create_ready_session's answer keys per outcome, closed (34.10 (g)(1)). */
66
74
  export declare const CREATE_READY_SESSION_ANSWERS: Readonly<{
67
75
  readonly created: readonly ["outcome", "session_id"];
@@ -91,12 +91,25 @@ export const DISPATCH_SESSION_CID_PREFIX = "dispatch-";
91
91
  export const DISPATCH_SESSION_NAME_PREFIX = "job-";
92
92
  export const CREATE_READY_SESSION_REFUSALS = [
93
93
  "no_dispatch_agent",
94
+ // amendment 63 (signed off by Wassim 2026-11-05): neither the seal's confirmer nor the dispatch agent's
95
+ // creator is a user (NULL, empty or scheme-prefixed, such as github:<login>); nothing written
96
+ "dispatch_owner_missing",
94
97
  "seal_not_found",
95
98
  "cell_unmapped",
96
99
  // 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
97
100
  "charter_mismatch",
98
101
  ];
99
102
  export const CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
103
+ /**
104
+ * Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
105
+ * api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
106
+ * public.durable_is_user_creator. A creator is a user unless it is null, empty or scheme-prefixed
107
+ * (contains ':', as github:<login> does). No id shape is read: uuids, Zitadel numeric ids and
108
+ * test-mode harness-<x> are users, so an identity-provider change does not move it.
109
+ */
110
+ export function isUserCreator(createdBy) {
111
+ return typeof createdBy === "string" && createdBy !== "" && !createdBy.includes(":");
112
+ }
100
113
  /** durable_create_ready_session's answer keys per outcome, closed (34.10 (g)(1)). */
101
114
  export const CREATE_READY_SESSION_ANSWERS = Object.freeze({
102
115
  created: ["outcome", "session_id"],
@@ -737,7 +737,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
737
737
  OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off"];
738
738
  ORG_POLICY_LIMIT_KEYS: readonly ["per_session_budget_usd", "live_lease_cap"];
739
739
  POOL_CLAIM_FIELDS: readonly ["contract_sha256", "contract_version", "cap"];
740
- CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "seal_not_found", "cell_unmapped", "charter_mismatch"];
740
+ CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "dispatch_owner_missing", "seal_not_found", "cell_unmapped", "charter_mismatch"];
741
741
  CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused"];
742
742
  CREATE_READY_SESSION_ANSWERS: Readonly<{
743
743
  readonly created: readonly ["outcome", "session_id"];
@@ -27,6 +27,15 @@
27
27
  * - agent_sessions.job_failures (INTEGER NOT NULL DEFAULT 0, CHECK >= 0): the pool job's accepted
28
28
  * runner_failed count, never reset. Separate from resume_failures, so a lease lapse never spends
29
29
  * a job retry and a job retry never counts towards respawn_failed.
30
+ *
31
+ * OIDC repo binding (mnemom-test#1329; S1 amendment approved by the Durable lead; mnemom-api#4530
32
+ * migration durable_m1_doors_48_w17_oidc_repo_binding), appended to its table:
33
+ * - agent_executors.oidc_repo (TEXT NULL): the owner/name repo whose GitHub Actions token minted
34
+ * the executor in the OIDC exchange (durable_apply_oidc_enrollment); NULL on every other row;
35
+ * - agent_executors.oidc_bound (BOOLEAN NOT NULL DEFAULT false): true only on a row the OIDC
36
+ * exchange wrote with oidc_repo. A bound executor claims only sessions whose dispatch seal names
37
+ * that repo, else 403 seal_repo_mismatch (SEAL_REPO_MISMATCH_CAUSES); an unbound row (every
38
+ * row written before the migration) is not checked.
30
39
  */
31
40
  export declare const M1_TABLES: readonly ["agent_sessions", "durable_lease_keys", "agent_session_charters", "agent_session_events", "durable_event_kinds", "agent_session_chains", "agent_executors", "agent_durable_profiles", "durable_cell_state", "durable_org_policy", "durable_user_policy", "durable_approvals", "durable_approval_attestations"];
32
41
  export type M1Table = (typeof M1_TABLES)[number];
@@ -28,6 +28,15 @@
28
28
  * - agent_sessions.job_failures (INTEGER NOT NULL DEFAULT 0, CHECK >= 0): the pool job's accepted
29
29
  * runner_failed count, never reset. Separate from resume_failures, so a lease lapse never spends
30
30
  * a job retry and a job retry never counts towards respawn_failed.
31
+ *
32
+ * OIDC repo binding (mnemom-test#1329; S1 amendment approved by the Durable lead; mnemom-api#4530
33
+ * migration durable_m1_doors_48_w17_oidc_repo_binding), appended to its table:
34
+ * - agent_executors.oidc_repo (TEXT NULL): the owner/name repo whose GitHub Actions token minted
35
+ * the executor in the OIDC exchange (durable_apply_oidc_enrollment); NULL on every other row;
36
+ * - agent_executors.oidc_bound (BOOLEAN NOT NULL DEFAULT false): true only on a row the OIDC
37
+ * exchange wrote with oidc_repo. A bound executor claims only sessions whose dispatch seal names
38
+ * that repo, else 403 seal_repo_mismatch (SEAL_REPO_MISMATCH_CAUSES); an unbound row (every
39
+ * row written before the migration) is not checked.
31
40
  */
32
41
  import { canonicalJson, sha256Hex, UUID_RE } from "./canonical.js";
33
42
  import { CANONICAL_CELL_IDS } from "./cell-id.js";
@@ -150,6 +159,8 @@ export const M1_COLUMNS = {
150
159
  "risk_line_version",
151
160
  "created_at",
152
161
  "scope",
162
+ "oidc_repo",
163
+ "oidc_bound",
153
164
  ],
154
165
  agent_durable_profiles: [
155
166
  "id",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.38-next.2",
3
+ "version": "0.17.38",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {