@agentcomms/events-daemon 0.17.0 → 0.18.0

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/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as createRule$1, $t as assertSocketPathFits, A as normaliseWhatsAppSourceOptions, At as deliveriesList, B as removeTarget$1, Bt as ControlFrameReader, C as canonicalSseSubscriber, Ct as assertDoctorReportV1, D as normaliseResendSourceOptions, Dt as isActivationFailureCode, E as normaliseGmailSourceOptions, Et as ACTIVATION_FAILURE, F as canonicalJudgeBudget, Ft as catalogueShow, G as removeSubscriber, Gt as negotiateVersion, H as updateTarget, Ht as encodeControlFrame, I as canonicalJudgeKindEnablement, It as approvalView, J as stop, Jt as processIsLive, K as subscribersList, Kt as mayRecoverStaleInstance, L as judgeKindDefinition, Lt as EventControlClient, M as JUDGE_BUDGET_ID, Mt as heldList, N as JUDGE_KIND_DEFINITIONS, Nt as retryDelivery, O as normaliseSlackSourceOptions, Ot as disableAll, P as canonicalJudge, Pt as catalogueList, Q as sourcesList, Qt as assertControlSupported, R as createEventsMcpServer, Rt as probeControl, S as normaliseActivationDocument, St as enableAll, T as classifySourceOptionChange, Tt as doctor, U as targetTest, Ut as isControlHello, V as targetsList, Vt as controlFailure, W as addSubscriber, Wt as isControlRequest, X as loadSqlite, Xt as removeInstance, Y as status, Yt as readInstance, Z as sourceShow$1, Zt as writeInstance, _ as activationDocumentDigest, _t as judgesList, an as ensureOwnerOnlyFile, at as updateRule, b as canonicalTarget, bt as updateBudget, ct as pause, d as B3_SECRET_TERMINAL_ORIGIN, dt as budgetShow, en as controlEndpoint, et as disableRule$1, f as isB3ControlOperation, ft as disableJudgeKind, g as isOwnerControlWireOperation, gt as judgeTest, h as OWNER_CONTROL_WIRES, ht as judgeModelStatus, in as ensureEventSocketDirectory, it as rulesList, j as APPROVED_TYPESAFE_MODELS, jt as dropDelivery, k as normaliseSourceOptions, kt as decideHeld, lt as resume, m as renderB3ApprovalPreviewV1, mt as judgeKindsList, nn as verifyPrivateSocketDirectory, nt as removeRule, on as eventPaths, ot as ruleTest, p as isB3SecretOperation, pt as enableJudgeKind, q as updateSubscriber, qt as newInstanceRecord, rn as ensureEventPaths, rt as ruleShow$1, sn as installErrorSink, st as ruleTestRetained, tn as verifyControlSocket, tt as enableRule, ut as addJudge, v as canonicalFullRuleDocument, vt as removeJudge, w as sseDeliveryTarget, wt as assertDoctorReportV2, x as disclosureBindingFor, xt as updateJudge, y as canonicalSubscriber, yt as removeJudgeModel, z as addTarget, zt as CONTROL_PROTOCOL_VERSIONS } from "./b3-contract-DFxcHY0I.mjs";
2
+ import { $ as sourcesList, $t as assertControlSupported, A as normaliseWhatsAppSourceOptions, At as decideHeld, B as addTarget, Bt as CONTROL_PROTOCOL_VERSIONS, C as canonicalSseSubscriber, Ct as enableAll, D as normaliseResendSourceOptions, Dt as ACTIVATION_FAILURE, E as normaliseGmailSourceOptions, Et as doctor, F as canonicalJudgeBudget, Ft as catalogueList, G as addSubscriber, Gt as isControlRequest, H as targetsList, Ht as controlFailure, I as canonicalJudgeKindEnablement, It as catalogueShow, J as updateSubscriber, Jt as newInstanceRecord, K as removeSubscriber, Kt as negotiateVersion, L as judgeKindDefinition, Lt as approvalView, M as JUDGE_BUDGET_ID, Mt as dropDelivery, N as JUDGE_KIND_DEFINITIONS, Nt as heldList, O as normaliseSlackSourceOptions, Ot as isActivationFailureCode, P as canonicalJudge, Pt as retryDelivery, Q as sourceShow$1, Qt as writeInstance, R as createEventsMcpServer, Rt as EventControlClient, S as normaliseActivationDocument, St as updateJudge, T as classifySourceOptionChange, Tt as assertDoctorReportV2, U as updateTarget, Ut as encodeControlFrame, V as removeTarget$1, Vt as ControlFrameReader, W as targetTest, Wt as isControlHello, X as status, Xt as readInstance, Y as stop, Yt as processIsLive, Z as loadSqlite, Zt as removeInstance, _ as activationDocumentDigest, _t as judgeTest, an as ensureEventSocketDirectory, at as rulesList, b as canonicalTarget, bt as removeJudgeModel, cn as installErrorSink, ct as ruleTestRetained, d as B3_SECRET_TERMINAL_ORIGIN, dt as addJudge, en as assertSocketPathFits, et as createRule$1, f as isB3ControlOperation, ft as budgetShow, g as isOwnerControlWireOperation, gt as judgeModelStatus, h as OWNER_CONTROL_WIRES, ht as judgeKindsList, in as ensureEventPaths, it as ruleShow$1, j as APPROVED_TYPESAFE_MODELS, jt as deliveriesList, k as normaliseSourceOptions, kt as disableAll, lt as pause, m as renderB3ApprovalPreviewV1, mt as enableJudgeKind, nn as verifyControlSocket, nt as enableRule, on as ensureOwnerOnlyFile, ot as updateRule, p as isB3SecretOperation, pt as disableJudgeKind, q as subscribersList, qt as mayRecoverStaleInstance, rn as verifyPrivateSocketDirectory, rt as removeRule, sn as eventPaths, st as ruleTest, tn as controlEndpoint, tt as disableRule$1, ut as resume, v as canonicalFullRuleDocument, vt as judgesList, w as sseDeliveryTarget, wt as assertDoctorReportV1, x as disclosureBindingFor, xt as updateBudget, y as canonicalSubscriber, yt as removeJudge, z as VERSION, zt as probeControl } from "./b3-contract-B6UBBK7F.mjs";
3
3
  import { a as EventDomainError, d as isJudgeKind, f as validateLocalEndpoint, g as parseIpLiteral, h as assertApprovedAddressSet, i as webhookDeliveryTarget, l as JUDGE_KIND_TYPESAFE, m as assertAddressAllowed, n as canonicalWebhookTarget, o as JUDGE_KINDS, p as AddressPolicyError, r as secretWebhookUrlDescriptor, s as JUDGE_KIND_LAYA, t as canonicalSecretWebhookUrl, u as assertJudgeKindEnabled } from "./webhook-target-DyDTwWhV.mjs";
4
4
  import { a as WriterQuiescer, c as openEventSecretStore, o as withEventWriterLease, r as RecordStorageError, s as migrateEventSecrets, t as EventRecordCipher } from "./records-DsUxYBwb.mjs";
5
5
  import process$1 from "node:process";
@@ -3365,6 +3365,7 @@ var ImmutableVersions = class {
3365
3365
  assertEmbeddedVersions(rule) {
3366
3366
  const embedded = [
3367
3367
  ...rule.targets.map((target) => ({
3368
+ kind: "target",
3368
3369
  table: "target_versions",
3369
3370
  column: "target_id",
3370
3371
  id: target.targetId,
@@ -3372,6 +3373,7 @@ var ImmutableVersions = class {
3372
3373
  document: target
3373
3374
  })),
3374
3375
  ...rule.subscribers.map((subscriber) => ({
3376
+ kind: "subscriber",
3375
3377
  table: "subscriber_versions",
3376
3378
  column: "subscriber_id",
3377
3379
  id: subscriber.subscriberId,
@@ -3379,6 +3381,7 @@ var ImmutableVersions = class {
3379
3381
  document: subscriber
3380
3382
  })),
3381
3383
  ...rule.agentic === void 0 ? [] : [{
3384
+ kind: "judge",
3382
3385
  table: "judge_versions",
3383
3386
  column: "judge_id",
3384
3387
  id: rule.agentic.judge.judgeId,
@@ -3390,8 +3393,19 @@ var ImmutableVersions = class {
3390
3393
  const row = this.#database.prepare(`SELECT document FROM ${entry.table} WHERE ${entry.column} = ? AND version = ?`).get(entry.id, entry.version);
3391
3394
  if (row === void 0) throw new EventDomainError("VERSION_DOCUMENT_INVALID", `the rule names ${entry.id} version ${entry.version}, which is not a stored version`);
3392
3395
  if (row.document !== canonicalJson(entry.document)) throw new EventDomainError("VERSION_DOCUMENT_INVALID", `the rule carries a copy of ${entry.id} version ${entry.version} that differs from the stored version`);
3396
+ if (entry.kind === "judge" && this.#embeddedJudgeIsRevoked(entry.id, entry.version)) throw new EventDomainError("VERSION_DOCUMENT_INVALID", `the rule names revoked judge ${entry.id} version ${entry.version}`);
3393
3397
  }
3394
3398
  }
3399
+ #embeddedJudgeIsRevoked(judgeId, version) {
3400
+ return this.#database.prepare(`SELECT 1 AS present
3401
+ FROM judge_versions AS stored
3402
+ LEFT JOIN object_revocations AS revoked
3403
+ ON revoked.kind = 'judge'
3404
+ AND revoked.object_id = stored.judge_id
3405
+ AND revoked.version = stored.version
3406
+ WHERE stored.judge_id = ? AND stored.version = ?
3407
+ AND (stored.revoked_at IS NOT NULL OR revoked.revoked_at IS NOT NULL)`).get(judgeId, version) !== void 0;
3408
+ }
3395
3409
  insert(table, kind, objectId, version, document) {
3396
3410
  const id = versionId(objectId, version);
3397
3411
  const persisted = documentRecord(document);
@@ -7292,7 +7306,7 @@ function readReservation(database, id) {
7292
7306
  function readReservationForSourceWork(database, workId, scope) {
7293
7307
  return database.prepare(`SELECT * FROM judge_budget_reservations
7294
7308
  WHERE work_id = ? AND work_scope = 'source-owned' AND source = ? AND account_id = ?
7295
- AND event_id = ? AND rule_id = ? AND rule_version = ?`).get(workId, scope.source, scope.accountId, scope.eventId, scope.ruleId, scope.ruleVersion) ?? null;
7309
+ AND event_id = ? AND rule_id = ? AND rule_version = ? AND state IN ('reserved', 'in-flight')`).get(workId, scope.source, scope.accountId, scope.eventId, scope.ruleId, scope.ruleVersion) ?? null;
7296
7310
  }
7297
7311
  function reservationView(row) {
7298
7312
  const scope = sourceScope(row);
@@ -7399,8 +7413,7 @@ function reserveJudgeWork(input) {
7399
7413
  activeKind(input.store.database, judge.kind);
7400
7414
  const limits = ruleAuthority(input.store.database, input.scope, judge);
7401
7415
  if (!sameCanonical(limits, input.limits)) refusal$2("JUDGE_AUTHORITY_CHANGED", "the supplied limits differ from the immutable rule authority");
7402
- const prior = readReservationForSourceWork(input.store.database, input.workId, input.scope);
7403
- if (prior?.state === "reserved" || prior?.state === "in-flight") refusal$2("JUDGE_WORK_OWNER_HELD", "a live judge reservation already owns this source work");
7416
+ if (readReservationForSourceWork(input.store.database, input.workId, input.scope) !== null) refusal$2("JUDGE_WORK_OWNER_HELD", "a live judge reservation already owns this source work");
7404
7417
  assertReservationLimits(input.store.database, {
7405
7418
  scope: input.scope,
7406
7419
  budget,
@@ -7410,15 +7423,6 @@ function reserveJudgeWork(input) {
7410
7423
  now: input.now,
7411
7424
  adding: true
7412
7425
  });
7413
- if (prior?.state === "released") {
7414
- input.store.database.prepare(`UPDATE judge_budget_reservations
7415
- SET estimated_input_tokens = ?, actual_input_tokens = NULL, state = 'reserved', concurrency_owned = 1,
7416
- reserved_at = ?, in_flight_at = NULL, settled_at = NULL, released_at = NULL, switch_generation = ?
7417
- WHERE id = ? AND state = 'released' AND concurrency_owned = 0`).run(estimate, input.now, input.switchGeneration, prior.id);
7418
- const reactivated = readReservation(input.store.database, prior.id);
7419
- if (reactivated === null || reactivated.state !== "reserved") refusal$2("JUDGE_AUTHORITY_CHANGED", "the released judge work could not be re-reserved");
7420
- return reservationView(reactivated);
7421
- }
7422
7426
  return insertReservation(input.store.database, {
7423
7427
  ...input,
7424
7428
  judge,
@@ -7477,12 +7481,14 @@ function beginJudgeTransport(input) {
7477
7481
  if (row === null || row.state !== "reserved") return false;
7478
7482
  try {
7479
7483
  const authority = assertReservationAuthority(input.store.database, row, input.now);
7480
- const scope = sourceScope(row) ?? {
7484
+ const source = sourceScope(row);
7485
+ const scope = source ?? {
7481
7486
  scope: "synthetic-test",
7482
7487
  testWorkId: row.test_work_id,
7483
7488
  judgeId: row.judge_id,
7484
7489
  judgeVersion: row.judge_version
7485
7490
  };
7491
+ if (source !== null) assertSourceReservationBoundary(input.store.database, source, row.switch_generation);
7486
7492
  assertReservationLimits(input.store.database, {
7487
7493
  scope,
7488
7494
  budget: authority.budget,
@@ -7492,6 +7498,8 @@ function beginJudgeTransport(input) {
7492
7498
  now: input.now,
7493
7499
  adding: false
7494
7500
  });
7501
+ assertHostedKeyGeneration(input.store.database, row, input.hostedKey);
7502
+ input.assertBeforeTransition?.();
7495
7503
  } catch (error) {
7496
7504
  releaseReservationInTransaction(input.store.database, row.id, input.now);
7497
7505
  refused = true;
@@ -7506,6 +7514,67 @@ function beginJudgeTransport(input) {
7506
7514
  return began;
7507
7515
  }
7508
7516
  /**
7517
+ * Couples the durable I/O transition to the sealed transport's synchronous gate. Hosted work enters at DNS; a
7518
+ * literal-loopback judge has no DNS action and enters at TCP. Every later gate rechecks the complete live authority.
7519
+ */
7520
+ var JudgeReservationIoGate = class {
7521
+ #store;
7522
+ #reservationId;
7523
+ #judgeKind;
7524
+ #now;
7525
+ #hostedKey;
7526
+ #entered = false;
7527
+ #firstGateError;
7528
+ constructor(input) {
7529
+ this.#store = input.store;
7530
+ this.#reservationId = input.reservationId;
7531
+ this.#judgeKind = input.judgeKind;
7532
+ this.#now = input.now;
7533
+ this.#hostedKey = input.hostedKey;
7534
+ }
7535
+ get firstGateError() {
7536
+ return this.#firstGateError;
7537
+ }
7538
+ finalGate = (phase, assertBeforeTransition) => {
7539
+ const firstPhysicalPhase = this.#judgeKind === "typesafe" ? "dns" : "tcp";
7540
+ if (!this.#entered) {
7541
+ if (phase !== firstPhysicalPhase) {
7542
+ assertBeforeTransition?.();
7543
+ return;
7544
+ }
7545
+ let deadlineRefused = false;
7546
+ try {
7547
+ if (!beginJudgeTransport({
7548
+ store: this.#store,
7549
+ reservationId: this.#reservationId,
7550
+ now: this.#now(),
7551
+ ...this.#hostedKey === void 0 ? {} : { hostedKey: this.#hostedKey },
7552
+ assertBeforeTransition: () => {
7553
+ try {
7554
+ assertBeforeTransition?.();
7555
+ } catch (error) {
7556
+ deadlineRefused = true;
7557
+ throw error;
7558
+ }
7559
+ }
7560
+ })) refusal$2("JUDGE_AUTHORITY_CHANGED", "the judge reservation was lost before physical I/O");
7561
+ } catch (error) {
7562
+ if (!deadlineRefused) this.#firstGateError = error;
7563
+ throw error;
7564
+ }
7565
+ this.#entered = true;
7566
+ return;
7567
+ }
7568
+ assertJudgeReservationStillInFlight({
7569
+ store: this.#store,
7570
+ reservationId: this.#reservationId,
7571
+ now: this.#now(),
7572
+ ...this.#hostedKey === void 0 ? {} : { hostedKey: this.#hostedKey }
7573
+ });
7574
+ assertBeforeTransition?.();
7575
+ };
7576
+ };
7577
+ /**
7509
7578
  * The synchronous final transport/decision fence. It reuses the one reservation authority reader rather than
7510
7579
  * manufacturing a second reservation path, and is safe to call immediately beside each Task-6 I/O action.
7511
7580
  */
@@ -7513,21 +7582,23 @@ function assertJudgeReservationInFlightInTransaction(database, reservationId, no
7513
7582
  const row = readReservation(database, reservationId);
7514
7583
  if (row === null || row.state !== "in-flight" || row.concurrency_owned !== 1) refusal$2("JUDGE_AUTHORITY_CHANGED", "the judge reservation is no longer in flight");
7515
7584
  assertReservationAuthority(database, row, now);
7516
- if (hostedKey !== void 0) {
7517
- if (row.judge_id !== hostedKey.judgeId || row.judge_version !== hostedKey.judgeVersion) refusal$2("JUDGE_AUTHORITY_CHANGED", "the hosted judge key does not belong to this reservation");
7518
- if (database.prepare(`SELECT 1 AS present
7519
- FROM event_secret_generations AS generation
7520
- JOIN judge_versions AS judge
7521
- ON judge.judge_id = generation.owner_id AND judge.version = generation.owner_version
7522
- WHERE generation.owner_kind = 'judge' AND generation.purpose = 'judge-key'
7523
- AND generation.owner_id = ? AND generation.owner_version = ? AND generation.generation = ?
7524
- AND generation.lifecycle = 'current' AND generation.owner_digest = judge.digest
7525
- AND judge.revoked_at IS NULL`).get(hostedKey.judgeId, hostedKey.judgeVersion, hostedKey.generation) === void 0) refusal$2("JUDGE_AUTHORITY_CHANGED", "the hosted judge key generation is no longer current");
7526
- }
7585
+ assertHostedKeyGeneration(database, row, hostedKey);
7527
7586
  const scope = sourceScope(row);
7528
7587
  if (scope === null) return;
7529
7588
  assertSourceReservationBoundary(database, scope, row.switch_generation);
7530
7589
  }
7590
+ function assertHostedKeyGeneration(database, row, hostedKey) {
7591
+ if (hostedKey === void 0) return;
7592
+ if (row.judge_id !== hostedKey.judgeId || row.judge_version !== hostedKey.judgeVersion) refusal$2("JUDGE_AUTHORITY_CHANGED", "the hosted judge key does not belong to this reservation");
7593
+ if (database.prepare(`SELECT 1 AS present
7594
+ FROM event_secret_generations AS generation
7595
+ JOIN judge_versions AS judge
7596
+ ON judge.judge_id = generation.owner_id AND judge.version = generation.owner_version
7597
+ WHERE generation.owner_kind = 'judge' AND generation.purpose = 'judge-key'
7598
+ AND generation.owner_id = ? AND generation.owner_version = ? AND generation.generation = ?
7599
+ AND generation.lifecycle = 'current' AND generation.owner_digest = judge.digest
7600
+ AND judge.revoked_at IS NULL`).get(hostedKey.judgeId, hostedKey.judgeVersion, hostedKey.generation) === void 0) refusal$2("JUDGE_AUTHORITY_CHANGED", "the hosted judge key generation is no longer current");
7601
+ }
7531
7602
  function assertSourceReservationBoundary(database, scope, switchGeneration) {
7532
7603
  const settings = database.prepare("SELECT enabled, paused, switch_generation FROM event_settings WHERE singleton = 1").get();
7533
7604
  if (settings?.paused === 1) refusal$2("JUDGE_PAUSED", "the event switch is paused before the judge boundary");
@@ -7540,20 +7611,10 @@ function assertJudgeReservationStillInFlight(input) {
7540
7611
  assertJudgeReservationInFlightInTransaction(input.store.database, input.reservationId, input.now, input.hostedKey);
7541
7612
  });
7542
7613
  }
7543
- function releaseReservationInTransaction(database, reservationId, now, mayReleaseInFlight = false) {
7614
+ function releaseReservationInTransaction(database, reservationId, now) {
7544
7615
  return database.prepare(`UPDATE judge_budget_reservations
7545
7616
  SET state = 'released', concurrency_owned = 0, released_at = ?
7546
- WHERE id = ? AND concurrency_owned = 1
7547
- AND state != 'settled' AND state != 'released'
7548
- AND state = CASE WHEN ? = 1 THEN state ELSE 'reserved' END`).run(now, reservationId, mayReleaseInFlight ? 1 : 0).changes === 1;
7549
- }
7550
- /**
7551
- * Pre-I/O loss is free: a reserved call releases normally. `beforeIo` is an intentionally narrow transport proof
7552
- * that lets a final fence refusal release the just-marked in-flight row too, because no DNS, TCP, TLS, or raw write
7553
- * was entered and therefore there is no provider work to charge.
7554
- */
7555
- function releaseJudgeWork(input) {
7556
- return input.store.immediate(() => releaseReservationInTransaction(input.store.database, input.reservationId, input.now, input.beforeIo === true));
7617
+ WHERE id = ? AND concurrency_owned = 1 AND state = 'reserved'`).run(now, reservationId).changes === 1;
7557
7618
  }
7558
7619
  function terminalReason(result) {
7559
7620
  if (result === "unknown" || result === "timeout" || result === "transport-failure") return "judge-io-unknown";
@@ -7607,6 +7668,24 @@ function settleJudgeWork(input) {
7607
7668
  testFailpoint: input.testFailpoint
7608
7669
  }));
7609
7670
  }
7671
+ /**
7672
+ * Closes a refused transport from the durable boundary state, not from the transport's advisory `preIo` flag. A
7673
+ * refusal that reached the synchronous physical-I/O gate is already in flight and must settle pessimistically;
7674
+ * every earlier refusal is still reserved and releases without a rolling charge.
7675
+ */
7676
+ function finalizeJudgeTransportRefusal(input) {
7677
+ return input.store.immediate(() => {
7678
+ const row = readReservation(input.store.database, input.reservationId);
7679
+ if (row === null) return null;
7680
+ if (row.state === "released" || row.state === "settled") return row.state;
7681
+ if (row.state === "reserved") return releaseReservationInTransaction(input.store.database, row.id, input.now) ? "released" : null;
7682
+ return settleReservationInTransaction(input.store.database, {
7683
+ reservationId: row.id,
7684
+ result: input.result,
7685
+ now: input.now
7686
+ }) ? "settled" : null;
7687
+ });
7688
+ }
7610
7689
  /** Used only by the shared recovery closure; no other module may reimplement these state transitions. */
7611
7690
  function recoverOneJudgeReservationInTransaction(database, reservationId, now) {
7612
7691
  const row = readReservation(database, reservationId);
@@ -11468,6 +11547,119 @@ const EVENT_MIGRATIONS = [
11468
11547
  "CREATE INDEX account_revocations_account_source ON account_revocations(account_id, source)",
11469
11548
  "UPDATE meta SET value = '21' WHERE key = 'schema_version'"
11470
11549
  ]
11550
+ },
11551
+ {
11552
+ version: 22,
11553
+ name: "event-judge-budget-attempts-v1",
11554
+ statements: [
11555
+ `CREATE TABLE judge_budget_reservations_v22 (
11556
+ id TEXT PRIMARY KEY,
11557
+ work_id TEXT NOT NULL,
11558
+ work_scope TEXT NOT NULL CHECK (work_scope IN ('source-owned', 'synthetic-test')),
11559
+ source TEXT,
11560
+ account_id TEXT,
11561
+ event_id TEXT,
11562
+ rule_id TEXT,
11563
+ rule_version INTEGER CHECK (rule_version > 0),
11564
+ judge_id TEXT NOT NULL,
11565
+ judge_version INTEGER NOT NULL CHECK (judge_version > 0),
11566
+ judge_kind TEXT NOT NULL CHECK (judge_kind IN (${JUDGE_KIND_SQL_VALUES})),
11567
+ test_work_id TEXT,
11568
+ budget_version INTEGER NOT NULL REFERENCES judge_budget_versions(version),
11569
+ provider TEXT,
11570
+ global_ledger_key TEXT NOT NULL CHECK (global_ledger_key = 'installation'),
11571
+ state TEXT NOT NULL CHECK (state IN ('reserved', 'in-flight', 'settled', 'released')),
11572
+ estimated_input_tokens INTEGER NOT NULL CHECK (estimated_input_tokens >= 0),
11573
+ actual_input_tokens INTEGER CHECK (actual_input_tokens >= 0),
11574
+ concurrency_owned INTEGER NOT NULL CHECK (concurrency_owned IN (0, 1)),
11575
+ reserved_at INTEGER NOT NULL,
11576
+ in_flight_at INTEGER,
11577
+ settled_at INTEGER,
11578
+ released_at INTEGER,
11579
+ switch_generation INTEGER NOT NULL CHECK (switch_generation >= 0),
11580
+ test_authority_rule_id TEXT,
11581
+ test_authority_rule_version INTEGER CHECK (test_authority_rule_version > 0),
11582
+ test_authority_activation_id TEXT,
11583
+ test_authority_approval_id TEXT,
11584
+ test_authority_challenge_hash TEXT CHECK (
11585
+ test_authority_challenge_hash IS NULL
11586
+ OR (length(test_authority_challenge_hash) = 64
11587
+ AND test_authority_challenge_hash NOT GLOB '*[^0-9a-f]*')
11588
+ ),
11589
+ test_authority_challenge_origin TEXT CHECK (test_authority_challenge_origin IN ('terminal', 'view')),
11590
+ test_authority_challenge_expires_at INTEGER,
11591
+ FOREIGN KEY (rule_id, rule_version) REFERENCES rule_versions(rule_id, version),
11592
+ FOREIGN KEY (judge_id, judge_version) REFERENCES judge_versions(judge_id, version),
11593
+ CHECK (
11594
+ (work_scope = 'source-owned'
11595
+ AND source IS NOT NULL
11596
+ AND source IN ('gmail', 'slack', 'resend', 'whatsapp')
11597
+ AND account_id IS NOT NULL
11598
+ AND event_id IS NOT NULL
11599
+ AND rule_id IS NOT NULL
11600
+ AND rule_version IS NOT NULL
11601
+ AND test_work_id IS NULL
11602
+ AND provider IS NOT NULL)
11603
+ OR
11604
+ (work_scope = 'synthetic-test'
11605
+ AND source IS NULL
11606
+ AND account_id IS NULL
11607
+ AND event_id IS NULL
11608
+ AND rule_id IS NULL
11609
+ AND rule_version IS NULL
11610
+ AND test_work_id IS NOT NULL
11611
+ AND provider IS NULL)
11612
+ )
11613
+ ) STRICT`,
11614
+ `INSERT INTO judge_budget_reservations_v22
11615
+ (id, work_id, work_scope, source, account_id, event_id, rule_id, rule_version, judge_id, judge_version,
11616
+ judge_kind, test_work_id, budget_version, provider, global_ledger_key, state, estimated_input_tokens,
11617
+ actual_input_tokens, concurrency_owned, reserved_at, in_flight_at, settled_at, released_at, switch_generation,
11618
+ test_authority_rule_id, test_authority_rule_version, test_authority_activation_id, test_authority_approval_id,
11619
+ test_authority_challenge_hash, test_authority_challenge_origin, test_authority_challenge_expires_at)
11620
+ SELECT id, work_id, work_scope, source, account_id, event_id, rule_id, rule_version, judge_id, judge_version,
11621
+ judge_kind, test_work_id, budget_version, provider, global_ledger_key, state, estimated_input_tokens,
11622
+ actual_input_tokens, concurrency_owned, reserved_at, in_flight_at, settled_at, released_at,
11623
+ switch_generation, test_authority_rule_id, test_authority_rule_version, test_authority_activation_id,
11624
+ test_authority_approval_id, test_authority_challenge_hash, test_authority_challenge_origin,
11625
+ test_authority_challenge_expires_at
11626
+ FROM judge_budget_reservations`,
11627
+ "DROP TABLE judge_budget_reservations",
11628
+ "ALTER TABLE judge_budget_reservations_v22 RENAME TO judge_budget_reservations",
11629
+ `CREATE UNIQUE INDEX judge_budget_reservations_live_work_identity
11630
+ ON judge_budget_reservations(work_id)
11631
+ WHERE state IN ('reserved', 'in-flight')`,
11632
+ `CREATE INDEX judge_budget_reservations_rule_rolling
11633
+ ON judge_budget_reservations(rule_id, reserved_at)
11634
+ WHERE work_scope = 'source-owned'`,
11635
+ `CREATE INDEX judge_budget_reservations_provider_rolling
11636
+ ON judge_budget_reservations(provider, reserved_at)
11637
+ WHERE provider IS NOT NULL`,
11638
+ `CREATE INDEX judge_budget_reservations_global_rolling
11639
+ ON judge_budget_reservations(global_ledger_key, reserved_at)`,
11640
+ `CREATE UNIQUE INDEX judge_budget_reservations_source_scope
11641
+ ON judge_budget_reservations(source, account_id, event_id, rule_id, rule_version)
11642
+ WHERE work_scope = 'source-owned' AND state IN ('reserved', 'in-flight')`,
11643
+ `CREATE INDEX judge_budget_reservations_synthetic_scope
11644
+ ON judge_budget_reservations(test_work_id, judge_id, judge_version, state)
11645
+ WHERE work_scope = 'synthetic-test'`,
11646
+ `CREATE INDEX judge_budget_reservations_kind_scope
11647
+ ON judge_budget_reservations(judge_kind, work_scope, state)`,
11648
+ `CREATE INDEX judge_budget_reservations_synthetic_authority
11649
+ ON judge_budget_reservations(test_authority_rule_id, test_authority_rule_version, test_authority_activation_id,
11650
+ test_authority_approval_id, test_authority_challenge_hash)
11651
+ WHERE work_scope = 'synthetic-test'`,
11652
+ `CREATE TRIGGER judge_budget_reservations_state_machine
11653
+ BEFORE UPDATE OF state ON judge_budget_reservations
11654
+ WHEN NEW.state <> OLD.state AND NOT (
11655
+ (OLD.state = 'reserved' AND NEW.state IN ('in-flight', 'released'))
11656
+ OR (OLD.state = 'in-flight' AND NEW.state = 'settled')
11657
+ )
11658
+ BEGIN
11659
+ SELECT RAISE(ABORT, 'invalid judge reservation state transition');
11660
+ END`,
11661
+ "UPDATE meta SET value = '22' WHERE key = 'schema_version'"
11662
+ ]
11471
11663
  }
11472
11664
  ];
11473
11665
  function initialiseLedger(database) {
@@ -11477,12 +11669,32 @@ function initialiseLedger(database) {
11477
11669
  applied_at INTEGER NOT NULL
11478
11670
  ) STRICT`);
11479
11671
  }
11672
+ /** The highest schema version a database's ledger records, or 0 when it has no ledger yet. It only reads. */
11673
+ function recordedSchemaVersion(database) {
11674
+ if (database.prepare("SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get() === void 0) return 0;
11675
+ return database.prepare("SELECT MAX(version) AS version FROM schema_migrations").get().version ?? 0;
11676
+ }
11677
+ /** Refuses a recorded schema version newer than this binary's, naming both and nothing else. */
11678
+ function refuseNewerVersion(databaseVersion, migrations = EVENT_MIGRATIONS) {
11679
+ const binaryVersion = migrations.at(-1)?.version ?? 0;
11680
+ if (databaseVersion > binaryVersion) throw new Error(`event database schema version ${databaseVersion} is newer than this release's schema version ${binaryVersion}; only the newer release that migrated it can open it`);
11681
+ }
11682
+ /**
11683
+ * Refuses a database a newer agent-events has migrated, by reading its ledger and nothing else: no table is created and
11684
+ * nothing is written, so it runs before the opener sets a journal mode or this file creates its ledger. A database
11685
+ * with no ledger yet is new, and passes.
11686
+ */
11687
+ function refuseNewerSchema(database, migrations = EVENT_MIGRATIONS) {
11688
+ refuseNewerVersion(recordedSchemaVersion(database), migrations);
11689
+ }
11480
11690
  /** Applies ordered daemon-owned migrations, one BEGIN IMMEDIATE transaction per version. */
11481
11691
  function applyMigrations(database, migrations = EVENT_MIGRATIONS, hooks = {}) {
11692
+ refuseNewerSchema(database, migrations);
11482
11693
  initialiseLedger(database);
11694
+ for (const [index, migration] of migrations.entries()) if (migration.version !== index + 1) throw new Error("event migrations must have consecutive versions starting at 1");
11483
11695
  const applied = database.prepare("SELECT version, name FROM schema_migrations ORDER BY version").all();
11484
- for (const [index, migration] of migrations.entries()) {
11485
- if (migration.version !== index + 1) throw new Error("event migrations must have consecutive versions starting at 1");
11696
+ refuseNewerVersion(applied.at(-1)?.version ?? 0, migrations);
11697
+ for (const migration of migrations) {
11486
11698
  const recorded = applied.find((entry) => entry.version === migration.version);
11487
11699
  if (recorded) {
11488
11700
  if (recorded.name !== migration.name) throw new Error(`event migration ${migration.version} does not match its ledger name`);
@@ -11517,18 +11729,44 @@ function installationIdFor(database) {
11517
11729
  database.prepare("INSERT INTO meta (key, value) VALUES (?, ?)").run("installation_id", created);
11518
11730
  return created;
11519
11731
  }
11732
+ /** SQLite's primary result code for a read that has to write first: recovering what a crash interrupted. */
11733
+ const SQLITE_READONLY = 8;
11734
+ /**
11735
+ * The schema version a database records, read through a read-only connection, so that a database a newer agent-events
11736
+ * migrated is refused without this release ever opening it for writing. It waits out a lock as the writable opener
11737
+ * would. Null only when SQLite must first recover an interrupted transaction, which takes a writable connection: the
11738
+ * opener checks again there. Any other failure, a lock held past the wait among them, refuses the open as it is.
11739
+ */
11740
+ function recordedVersionReadOnly(Database, path, busyTimeoutMs = 5e3) {
11741
+ try {
11742
+ const probe = new Database(path, { readOnly: true });
11743
+ try {
11744
+ probe.exec(`PRAGMA busy_timeout = ${Math.trunc(busyTimeoutMs)}`);
11745
+ return recordedSchemaVersion(probe);
11746
+ } finally {
11747
+ probe.close();
11748
+ }
11749
+ } catch (error) {
11750
+ const code = error.errcode;
11751
+ if (typeof code === "number" && (code & 255) === SQLITE_READONLY) return null;
11752
+ throw error;
11753
+ }
11754
+ }
11520
11755
  /** Opens the daemon’s one local authority after the Task 4 owner-only path checks have succeeded. */
11521
11756
  async function openEventDatabase(options) {
11522
11757
  const { DatabaseSync: Database } = await loadSqlite();
11523
11758
  const paths = await ensureEventPaths(eventPaths(options.stateDir));
11524
11759
  await ensureOwnerOnlyFile(paths.database);
11760
+ const recorded = recordedVersionReadOnly(Database, paths.database);
11761
+ if (recorded !== null) refuseNewerVersion(recorded);
11525
11762
  const database = new Database(paths.database);
11526
11763
  try {
11764
+ database.exec("PRAGMA busy_timeout = 5000");
11765
+ refuseNewerSchema(database);
11527
11766
  database.exec("PRAGMA foreign_keys = ON");
11528
11767
  database.exec("PRAGMA journal_mode = WAL");
11529
11768
  database.exec("PRAGMA synchronous = FULL");
11530
11769
  database.exec("PRAGMA secure_delete = ON");
11531
- database.exec("PRAGMA busy_timeout = 5000");
11532
11770
  applyMigrations(database);
11533
11771
  return {
11534
11772
  database,
@@ -11841,16 +12079,20 @@ var ActivationRuntime = class {
11841
12079
  }
11842
12080
  /** A narrowing revocation is allowed to win over a claimed completion, but no other pointer mutation is. */
11843
12081
  async cancelForRevocation(input) {
12082
+ this.#store.immediate(() => {
12083
+ this.cancelForRevocationInTransaction(input);
12084
+ });
12085
+ }
12086
+ /** A caller already holding the removal transaction cancels every matching intent before revocation commits. */
12087
+ cancelForRevocationInTransaction(input) {
11844
12088
  const rows = this.#store.database.prepare("SELECT id, document FROM activation_intents WHERE status IN ('pending', 'pending-completion') ORDER BY id").all();
11845
- const cancelled = [];
12089
+ let cancelled = 0;
11846
12090
  for (const row of rows) {
11847
12091
  if (!this.#isRevokedBinding(JSON.parse(row.document), input)) continue;
11848
- cancelled.push(row.id);
12092
+ this.#cancel(row.id, ACTIVATION_FAILURE.AUTHORIZATION_REVOKED);
12093
+ cancelled += 1;
11849
12094
  }
11850
- if (cancelled.length === 0) return;
11851
- this.#store.immediate(() => {
11852
- for (const intentId of cancelled) this.#cancel(intentId, ACTIVATION_FAILURE.AUTHORIZATION_REVOKED);
11853
- });
12095
+ return cancelled;
11854
12096
  }
11855
12097
  /** Called by the kind-disable transaction; no pending kind approval may recreate the pointer after disable wins. */
11856
12098
  cancelForJudgeKindInTransaction(kind) {
@@ -11858,7 +12100,7 @@ var ActivationRuntime = class {
11858
12100
  let cancelled = 0;
11859
12101
  for (const row of rows) {
11860
12102
  const document = JSON.parse(row.document);
11861
- if (document.kind === "judge-kind" && document.enablement.kind === kind || document.kind === "rule" && document.rule.agentic?.judge.kind === kind) {
12103
+ if (document.kind === "judge-kind" && document.enablement.kind === kind || document.kind === "rule" && document.rule.agentic?.judge.kind === kind || document.kind === "enable-all" && this.#ruleVersionsBindJudgeKind(document.ruleVersions, kind)) {
11862
12104
  this.#cancel(row.id, ACTIVATION_FAILURE.AUTHORIZATION_REVOKED);
11863
12105
  cancelled += 1;
11864
12106
  }
@@ -11976,6 +12218,8 @@ var ActivationRuntime = class {
11976
12218
  const now = this.#now();
11977
12219
  const plannedAccounts = [...new Map(points.map((point) => [`${point.source}\u0000${point.accountId}`, point])).values()];
11978
12220
  this.#store.immediate(() => {
12221
+ this.#assertBoundRuleAuthority(document);
12222
+ this.#assertPreparationPlanLive(document, effect, replacementOfVersion);
11979
12223
  this.#assertJudgeActivationLive(document, effect);
11980
12224
  const latestRevocation = plannedAccounts.reduce((latest, point) => {
11981
12225
  const row = this.#store.database.prepare(`SELECT MAX(revoked_at) AS revoked_at FROM account_revocations
@@ -11988,9 +12232,28 @@ var ActivationRuntime = class {
11988
12232
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`).run(intentId, document.kind, canonicalJson(document), binding.digest, canonicalJson(effect), replacementOfVersion ?? null, canonicalJson(points), canonicalJson([...new Set(points.map((point) => `${point.accountId}:${point.positionScope}`))]), createdAt, createdAt);
11989
12233
  });
11990
12234
  const approval = await this.#approvals.createDisclosure(binding);
12235
+ let settled;
11991
12236
  this.#store.immediate(() => {
11992
- this.#store.database.prepare("UPDATE activation_intents SET approval_id = ?, updated_at = ? WHERE id = ? AND approval_id IS NULL").run(approval.approvalId, this.#now(), intentId);
12237
+ const latest = this.#intent(intentId);
12238
+ const linkApproval = () => this.#store.database.prepare("UPDATE activation_intents SET approval_id = ?, updated_at = ? WHERE id = ? AND approval_id IS NULL").run(approval.approvalId, this.#now(), intentId).changes;
12239
+ if (latest.status !== "pending") {
12240
+ if (linkApproval() !== 1) throw new CommsError("APPROVAL_VOID", "the cancelled activation could not retain its disclosure link");
12241
+ settled = new CommsError("APPROVAL_VOID", "the activation was cancelled before its approval could be linked");
12242
+ return;
12243
+ }
12244
+ try {
12245
+ this.#assertBoundRuleAuthority(document);
12246
+ this.#assertPreparationPlanLive(document, effect, replacementOfVersion);
12247
+ this.#assertJudgeActivationLive(document, effect);
12248
+ } catch (error) {
12249
+ if (linkApproval() !== 1) throw new CommsError("APPROVAL_VOID", "the stale activation could not retain its disclosure link");
12250
+ this.#cancel(intentId, ACTIVATION_FAILURE.APPROVAL_BINDING_DRIFT);
12251
+ settled = error;
12252
+ return;
12253
+ }
12254
+ if (this.#store.database.prepare("UPDATE activation_intents SET approval_id = ?, updated_at = ? WHERE id = ? AND status = 'pending' AND approval_id IS NULL").run(approval.approvalId, this.#now(), intentId).changes !== 1) throw new CommsError("APPROVAL_VOID", "the activation approval link changed before it could be written");
11993
12255
  });
12256
+ if (settled !== void 0) throw settled;
11994
12257
  return {
11995
12258
  intentId,
11996
12259
  approvalId: approval.approvalId,
@@ -12013,7 +12276,7 @@ var ActivationRuntime = class {
12013
12276
  if (allowIssuingChallengeResidue) this.#assertApprovalIssuingChallengeResidue(intent.approval_id);
12014
12277
  if (row.status === "cancelled") throw new CommsError("APPROVAL_VOID", "the activation was cancelled before completion");
12015
12278
  const document = this.#document(row);
12016
- this.#assertRuleJudgeKindEnabled(document);
12279
+ this.#assertBoundRuleAuthority(document);
12017
12280
  if (document.kind === "judge-budget" || document.kind === "judge-kind") try {
12018
12281
  this.#assertJudgeActivationLive(document, JSON.parse(row.effect));
12019
12282
  } catch (_error) {
@@ -12166,7 +12429,7 @@ var ActivationRuntime = class {
12166
12429
  settled = new CommsError("APPROVAL_VOID", "the activation completion deadline has passed");
12167
12430
  return;
12168
12431
  }
12169
- this.#assertRuleJudgeKindEnabled(document);
12432
+ this.#assertBoundRuleAuthority(document);
12170
12433
  const effect = JSON.parse(latest.effect);
12171
12434
  const settings = this.#switch();
12172
12435
  if ((document.kind === "rule" || document.kind === "enable-all") && settings.generation !== effect.switchGeneration) {
@@ -12280,12 +12543,10 @@ var ActivationRuntime = class {
12280
12543
  const document = this.#document(intent);
12281
12544
  const binding = disclosureBindingFor(intent.id, document);
12282
12545
  if (binding.digest !== intent.digest || activationDocumentDigest(document) !== intent.digest) throw new CommsError("APPROVAL_VOID", "the activation document changed after preparation");
12546
+ this.#assertBoundRuleAuthority(document);
12283
12547
  if (document.kind === "rule") {
12284
- const versions = new ImmutableVersions(this.#store.database);
12285
- const active = versions.activeVersion("rule", document.rule.ruleId);
12548
+ const active = new ImmutableVersions(this.#store.database).activeVersion("rule", document.rule.ruleId);
12286
12549
  if (active !== null && (intent.replacement_of_version === null || `${document.rule.ruleId}@${active.version}` !== intent.replacement_of_version)) throw new CommsError("APPROVAL_VOID", "the rule gained an active pointer after preparation");
12287
- const live = versions.prepareRule(document.rule.ruleId, document.rule.version);
12288
- if (canonicalJson(live) !== canonicalJson(document)) throw new CommsError("APPROVAL_VOID", "the rule changed after preparation");
12289
12550
  } else if (document.kind === "enable-all") {
12290
12551
  const current = this.#switch();
12291
12552
  const live = this.#store.database.prepare("SELECT object_id, version, current_cutover_id FROM active_versions WHERE kind = 'rule' ORDER BY object_id, version").all();
@@ -12319,12 +12580,48 @@ var ActivationRuntime = class {
12319
12580
  #judgeKindDisableGeneration(kind) {
12320
12581
  return this.#store.database.prepare("SELECT generation FROM judge_kind_disable_generations WHERE kind = ?").get(kind)?.generation ?? 0;
12321
12582
  }
12322
- /** The absence of the sole judge-kind pointer is disabled; deterministic-only rules need no kind check. */
12323
- #assertRuleJudgeKindEnabled(document) {
12324
- if (document.kind !== "rule" || document.rule.agentic === void 0) return;
12325
- const kind = document.rule.agentic.judge.kind;
12583
+ /** Re-read every exact bound rule, including its embedded judge revocation and kind pointer, at each authority gate. */
12584
+ #assertBoundRuleAuthority(document) {
12585
+ if (document.kind !== "rule" && document.kind !== "enable-all") return;
12326
12586
  const versions = new ImmutableVersions(this.#store.database);
12327
- assertJudgeKindEnabled(kind, versions.activeVersion("judge-kind", kind) !== null);
12587
+ if (document.kind === "rule") {
12588
+ const live = versions.prepareRule(document.rule.ruleId, document.rule.version);
12589
+ if (canonicalJson(live) !== canonicalJson(document)) throw new CommsError("APPROVAL_VOID", "the rule changed after preparation");
12590
+ return;
12591
+ }
12592
+ for (const entry of document.ruleVersions) versions.prepareRule(entry.ruleId, entry.ruleVersion);
12593
+ }
12594
+ /** Preparation may await local stores; the exact pointer plan captured before those awaits must still be current. */
12595
+ #assertPreparationPlanLive(document, effect, replacementOfVersion) {
12596
+ if (document.kind === "rule") {
12597
+ const active = new ImmutableVersions(this.#store.database).activeVersion("rule", document.rule.ruleId);
12598
+ if ((active === null ? null : `${document.rule.ruleId}@${active.version}`) !== (replacementOfVersion ?? null) || this.#switch().generation !== effect.switchGeneration) throw new CommsError("APPROVAL_VOID", "the rule activation pointer plan changed during preparation");
12599
+ return;
12600
+ }
12601
+ if (document.kind !== "enable-all") return;
12602
+ const settings = this.#switch();
12603
+ const live = this.#store.database.prepare("SELECT object_id, version, current_cutover_id FROM active_versions WHERE kind = 'rule' ORDER BY object_id, version").all();
12604
+ if (settings.enabled || settings.generation !== document.switchGeneration || settings.generation !== effect.switchGeneration || canonicalJson(live.map((row) => ({
12605
+ ruleId: row.object_id,
12606
+ ruleVersion: row.version
12607
+ }))) !== canonicalJson(document.ruleVersions) || canonicalJson(live.map((row) => ({
12608
+ ruleId: row.object_id,
12609
+ ruleVersion: row.version,
12610
+ cutoverId: row.current_cutover_id
12611
+ }))) !== canonicalJson(effect.currentCutovers ?? [])) throw new CommsError("APPROVAL_VOID", "the global enablement pointer plan changed during preparation");
12612
+ }
12613
+ /** A damaged captured rule row cannot prove that a kind disable is unrelated, so cancellation is fail-closed. */
12614
+ #ruleVersionsBindJudgeKind(entries, kind) {
12615
+ for (const entry of entries) {
12616
+ const row = this.#store.database.prepare("SELECT document FROM rule_versions WHERE rule_id = ? AND version = ?").get(entry.ruleId, entry.ruleVersion);
12617
+ if (row === void 0) return true;
12618
+ try {
12619
+ if (canonicalFullRuleDocument(JSON.parse(row.document)).agentic?.judge.kind === kind) return true;
12620
+ } catch {
12621
+ return true;
12622
+ }
12623
+ }
12624
+ return false;
12328
12625
  }
12329
12626
  #document(intent) {
12330
12627
  return JSON.parse(intent.document);
@@ -13583,10 +13880,10 @@ var B3OperatorControl = class {
13583
13880
  }).disable(kind);
13584
13881
  }
13585
13882
  async removeJudge(judgeId) {
13586
- await this.#activations?.cancelForRevocation({ judgeId });
13587
13883
  return this.#store.immediate(() => {
13588
13884
  const rows = this.#store.database.prepare("SELECT version FROM judge_versions WHERE judge_id = ? AND revoked_at IS NULL ORDER BY version").all(judgeId);
13589
13885
  if (rows.length === 0) throw new CommsError("NOT_FOUND", "the judge has no live version to remove");
13886
+ this.#activations?.cancelForRevocationInTransaction({ judgeId });
13590
13887
  const revokedAt = this.#now();
13591
13888
  const removedVersions = new Set(rows.map((row) => row.version));
13592
13889
  for (const row of rows) {
@@ -15547,9 +15844,9 @@ var JudgeTransport = class {
15547
15844
  let enteredIo = false;
15548
15845
  try {
15549
15846
  await this.#beforeGate(hooks, "dns");
15550
- this.#finalGate(hooks, "dns");
15551
- assertBeforeDeadline(deadline, this.#clock);
15847
+ this.#finalGate(hooks, "dns", deadline);
15552
15848
  enteredIo = call.judge.kind === JUDGE_KIND_TYPESAFE;
15849
+ assertBeforeDeadline(deadline, this.#clock);
15553
15850
  const connection = await beforeDeadline$1(preparePinnedConnection(call.judge.kind === "typesafe" ? {
15554
15851
  url: endpoint,
15555
15852
  addressMode: "global-only",
@@ -15562,9 +15859,9 @@ var JudgeTransport = class {
15562
15859
  }), deadline, this.#clock);
15563
15860
  assertBeforeDeadline(deadline, this.#clock);
15564
15861
  await this.#beforeGate(hooks, "tcp");
15565
- this.#finalGate(hooks, "tcp");
15566
- assertBeforeDeadline(deadline, this.#clock);
15862
+ this.#finalGate(hooks, "tcp", deadline);
15567
15863
  enteredIo = true;
15864
+ assertBeforeDeadline(deadline, this.#clock);
15568
15865
  const tcp = await beforeDeadline$1(this.#tcpConnect({
15569
15866
  host: connection.address,
15570
15867
  port: connection.port
@@ -15573,7 +15870,7 @@ var JudgeTransport = class {
15573
15870
  socket = tcp;
15574
15871
  if (connection.useTls) {
15575
15872
  await this.#beforeGate(hooks, "tls");
15576
- this.#finalGate(hooks, "tls");
15873
+ this.#finalGate(hooks, "tls", deadline);
15577
15874
  assertBeforeDeadline(deadline, this.#clock);
15578
15875
  socket = await beforeDeadline$1(this.#tlsConnect({
15579
15876
  socket: tcp,
@@ -15582,7 +15879,7 @@ var JudgeTransport = class {
15582
15879
  assertBeforeDeadline(deadline, this.#clock);
15583
15880
  }
15584
15881
  await this.#beforeGate(hooks, "write");
15585
- this.#finalGate(hooks, "write");
15882
+ this.#finalGate(hooks, "write", deadline);
15586
15883
  assertBeforeDeadline(deadline, this.#clock);
15587
15884
  socket.write(request);
15588
15885
  const body = await beforeDeadline$1(boundedResponse(socket), deadline, this.#clock);
@@ -15613,14 +15910,16 @@ var JudgeTransport = class {
15613
15910
  }
15614
15911
  }
15615
15912
  /**
15616
- * At every call site only the synchronous deadline check stands between this gate and the physical B2 network action;
15617
- * no await may be inserted between them.
15913
+ * The deadline is checked after the awaited preparation and again inside the durable production gate immediately
15914
+ * before its state transition. No await may be inserted between this gate and the physical B2 network action.
15618
15915
  */
15619
- #finalGate(hooks, phase) {
15916
+ #finalGate(hooks, phase, deadline) {
15917
+ assertBeforeDeadline(deadline, this.#clock);
15620
15918
  let decision;
15621
15919
  try {
15622
- decision = hooks.finalGate(phase);
15623
- } catch {
15920
+ decision = hooks.finalGate(phase, () => assertBeforeDeadline(deadline, this.#clock));
15921
+ } catch (error) {
15922
+ if (error instanceof DeadlineExceeded) throw error;
15624
15923
  throw new FenceRefused();
15625
15924
  }
15626
15925
  if (decision !== void 0) {
@@ -15798,33 +16097,19 @@ var JudgeTestControl = class {
15798
16097
  } catch (error) {
15799
16098
  return refusal(input.judgeId, input.version, error);
15800
16099
  }
15801
- try {
15802
- if (!beginJudgeTransport({
15803
- store: this.#store,
15804
- reservationId: reservation.id,
15805
- now: this.#now()
15806
- })) return {
15807
- kind: "refused",
15808
- judgeId: input.judgeId,
15809
- version: input.version,
15810
- code: "JUDGE_AUTHORITY_CHANGED"
15811
- };
15812
- } catch (error) {
15813
- return refusal(input.judgeId, input.version, error);
15814
- }
16100
+ const ioGate = new JudgeReservationIoGate({
16101
+ store: this.#store,
16102
+ reservationId: reservation.id,
16103
+ judgeKind: authority.judge.kind,
16104
+ now: this.#now,
16105
+ ...prepared.hostedKeyGeneration === null ? {} : { hostedKey: {
16106
+ judgeId: authority.judge.judgeId,
16107
+ judgeVersion: authority.judge.version,
16108
+ generation: prepared.hostedKeyGeneration
16109
+ } }
16110
+ });
15815
16111
  const result = await this.#transport.execute(call, {
15816
- finalGate: () => {
15817
- assertJudgeReservationStillInFlight({
15818
- store: this.#store,
15819
- reservationId: reservation.id,
15820
- now: this.#now(),
15821
- ...prepared.hostedKeyGeneration === null ? {} : { hostedKey: {
15822
- judgeId: authority.judge.judgeId,
15823
- judgeVersion: authority.judge.version,
15824
- generation: prepared.hostedKeyGeneration
15825
- } }
15826
- });
15827
- },
16112
+ finalGate: ioGate.finalGate,
15828
16113
  preparedRequest: prepared
15829
16114
  });
15830
16115
  if (result.kind === "answer") {
@@ -15844,27 +16129,13 @@ var JudgeTestControl = class {
15844
16129
  reservationId: reservation.id
15845
16130
  };
15846
16131
  }
15847
- if (result.preIo) {
15848
- releaseJudgeWork({
15849
- store: this.#store,
15850
- reservationId: reservation.id,
15851
- now: this.#now(),
15852
- beforeIo: true
15853
- });
15854
- return {
15855
- kind: "deferred",
15856
- judgeId: input.judgeId,
15857
- version: input.version,
15858
- code: result.code,
15859
- reservationId: reservation.id
15860
- };
15861
- }
15862
- settleJudgeWork({
16132
+ finalizeJudgeTransportRefusal({
15863
16133
  store: this.#store,
15864
16134
  reservationId: reservation.id,
15865
16135
  result: result.code === "OUTPUT_TOKEN_LIMIT_EXCEEDED" ? "output-token-limit" : result.code === "NETWORK_DEADLINE_EXCEEDED" ? "timeout" : "transport-failure",
15866
16136
  now: this.#now()
15867
16137
  });
16138
+ if (ioGate.firstGateError !== void 0) return refusal(input.judgeId, input.version, ioGate.firstGateError);
15868
16139
  return {
15869
16140
  kind: "deferred",
15870
16141
  judgeId: input.judgeId,
@@ -16586,61 +16857,44 @@ var JudgeCoordinator = class {
16586
16857
  reservationId: null
16587
16858
  };
16588
16859
  }
16589
- try {
16590
- if (!beginJudgeTransport({
16591
- store: this.#store,
16592
- reservationId: reservation.id,
16593
- now: this.#now()
16594
- })) return {
16595
- kind: "closed",
16596
- reason: "reservation-lost",
16597
- reservationId: reservation.id
16598
- };
16599
- } catch (error) {
16600
- if (error instanceof JudgeBudgetError && error.code === "JUDGE_PAUSED") return {
16601
- kind: "deferred",
16602
- reservationId: reservation.id
16603
- };
16604
- return {
16605
- kind: "closed",
16606
- reason: error instanceof Error && "code" in error ? String(error.code) : "authority-changed",
16607
- reservationId: reservation.id
16608
- };
16609
- }
16860
+ const ioGate = new JudgeReservationIoGate({
16861
+ store: this.#store,
16862
+ reservationId: reservation.id,
16863
+ judgeKind: agentic.judge.kind,
16864
+ now: this.#now,
16865
+ ...prepared.hostedKeyGeneration === null ? {} : { hostedKey: {
16866
+ judgeId: agentic.judge.judgeId,
16867
+ judgeVersion: agentic.judge.version,
16868
+ generation: prepared.hostedKeyGeneration
16869
+ } }
16870
+ });
16610
16871
  const result = await this.#transport.execute(call, {
16611
- finalGate: () => {
16612
- assertJudgeReservationStillInFlight({
16613
- store: this.#store,
16614
- reservationId: reservation.id,
16615
- ...prepared.hostedKeyGeneration === null ? {} : { hostedKey: {
16616
- judgeId: agentic.judge.judgeId,
16617
- judgeVersion: agentic.judge.version,
16618
- generation: prepared.hostedKeyGeneration
16619
- } }
16620
- });
16621
- },
16872
+ finalGate: ioGate.finalGate,
16622
16873
  preparedRequest: prepared
16623
16874
  });
16624
16875
  if (result.kind !== "answer") {
16625
- if (result.preIo === true) {
16626
- releaseJudgeWork({
16627
- store: this.#store,
16628
- reservationId: reservation.id,
16629
- now: this.#now(),
16630
- beforeIo: true
16631
- });
16632
- return {
16633
- kind: "deferred",
16634
- reservationId: reservation.id
16635
- };
16636
- }
16637
16876
  const settlement = result.code === "OUTPUT_TOKEN_LIMIT_EXCEEDED" ? "output-token-limit" : result.code === "NETWORK_DEADLINE_EXCEEDED" ? "timeout" : "unknown";
16638
- settleJudgeWork({
16877
+ const disposition = finalizeJudgeTransportRefusal({
16639
16878
  store: this.#store,
16640
16879
  reservationId: reservation.id,
16641
16880
  result: settlement,
16642
16881
  now: this.#now()
16643
16882
  });
16883
+ if (ioGate.firstGateError !== void 0) {
16884
+ if (ioGate.firstGateError instanceof JudgeBudgetError && ioGate.firstGateError.code === "JUDGE_PAUSED") return {
16885
+ kind: "deferred",
16886
+ reservationId: reservation.id
16887
+ };
16888
+ return {
16889
+ kind: "closed",
16890
+ reason: ioGate.firstGateError instanceof Error && "code" in ioGate.firstGateError ? String(ioGate.firstGateError.code) : "authority-changed",
16891
+ reservationId: reservation.id
16892
+ };
16893
+ }
16894
+ if (disposition === "released") return {
16895
+ kind: "deferred",
16896
+ reservationId: reservation.id
16897
+ };
16644
16898
  return {
16645
16899
  kind: "closed",
16646
16900
  reason: closedReason(result),
@@ -19851,7 +20105,7 @@ function sourceOfDeliveryRule(document) {
19851
20105
  /** Composes every Task 3/4 control behind the authenticated owner; public adapters never reach a runtime class. */
19852
20106
  function createOwnerB3ControlService(options) {
19853
20107
  const now = options.now ?? Date.now;
19854
- const judgeActivations = typeof options.activations.prepareJudgeBudget === "function" && typeof options.activations.prepareJudgeKind === "function" && typeof options.activations.cancelForJudgeKindInTransaction === "function" && typeof options.activations.cancelForRevocation === "function" ? options.activations : void 0;
20108
+ const judgeActivations = typeof options.activations.prepareJudgeBudget === "function" && typeof options.activations.prepareJudgeKind === "function" && typeof options.activations.cancelForJudgeKindInTransaction === "function" && typeof options.activations.cancelForRevocationInTransaction === "function" ? options.activations : void 0;
19855
20109
  const operator = new B3OperatorControl({
19856
20110
  store: options.store,
19857
20111
  ports: options.ports,
@@ -21176,6 +21430,10 @@ async function run(argv, deps = {}) {
21176
21430
  stdout: process.stdout,
21177
21431
  stderr: process.stderr
21178
21432
  };
21433
+ if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-V")) {
21434
+ streams.stdout.write(`${VERSION}\n`);
21435
+ return 0;
21436
+ }
21179
21437
  const env = deps.env ?? process.env;
21180
21438
  const program = new Command();
21181
21439
  let ran = false;