@agentcomms/events-daemon 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -9
- package/THIRD_PARTY_LICENSES +2 -2
- package/dist/{b3-contract-DFxcHY0I.mjs → b3-contract-BZIj-LWV.mjs} +4 -4
- package/dist/cli.mjs +493 -171
- package/dist/index.d.mts +119 -12
- package/dist/index.mjs +1 -1
- package/dist/{mcp-DXXb3Vv3-B0sHZfig.mjs → mcp-DIH4cS6P-YuKZfqTg.mjs} +181 -68
- package/dist/{stdio-CD7QQBAh.mjs → stdio-Dp1EE6ok.mjs} +28 -3
- package/package.json +11 -11
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
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-BZIj-LWV.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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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);
|
|
@@ -9476,6 +9555,26 @@ async function assertLiveEventAccount(config, scope) {
|
|
|
9476
9555
|
source: scope.source
|
|
9477
9556
|
} });
|
|
9478
9557
|
}
|
|
9558
|
+
/**
|
|
9559
|
+
* Every connected account a rule may name, loaded afresh: Gmail's inboxes by their stable `ibx_…` id and the other
|
|
9560
|
+
* channels' accounts by their `acc_…` id, each beside the name a person knows it by. What `sources list` shows, so
|
|
9561
|
+
* a first rule's account id can be read from it before any rule is active (CUE-474).
|
|
9562
|
+
*/
|
|
9563
|
+
async function connectedEventAccounts(config) {
|
|
9564
|
+
const current = await config.load();
|
|
9565
|
+
const found = [];
|
|
9566
|
+
for (const [account, inbox] of Object.entries(current.inboxes)) if (inbox.provider === "gmail") found.push({
|
|
9567
|
+
source: "gmail",
|
|
9568
|
+
accountId: inbox.id,
|
|
9569
|
+
account
|
|
9570
|
+
});
|
|
9571
|
+
for (const [account, entry] of Object.entries(current.accounts)) if (entry.platform === "slack" || entry.platform === "resend" || entry.platform === "whatsapp") found.push({
|
|
9572
|
+
source: entry.platform,
|
|
9573
|
+
accountId: entry.id,
|
|
9574
|
+
account
|
|
9575
|
+
});
|
|
9576
|
+
return found.sort((left, right) => `${left.source}\u0000${left.account}`.localeCompare(`${right.source}\u0000${right.account}`));
|
|
9577
|
+
}
|
|
9479
9578
|
/** The current stable ids for one source, loaded afresh at the boundary. */
|
|
9480
9579
|
async function liveEventAccountIds(config, source) {
|
|
9481
9580
|
const current = await config.load();
|
|
@@ -11468,6 +11567,119 @@ const EVENT_MIGRATIONS = [
|
|
|
11468
11567
|
"CREATE INDEX account_revocations_account_source ON account_revocations(account_id, source)",
|
|
11469
11568
|
"UPDATE meta SET value = '21' WHERE key = 'schema_version'"
|
|
11470
11569
|
]
|
|
11570
|
+
},
|
|
11571
|
+
{
|
|
11572
|
+
version: 22,
|
|
11573
|
+
name: "event-judge-budget-attempts-v1",
|
|
11574
|
+
statements: [
|
|
11575
|
+
`CREATE TABLE judge_budget_reservations_v22 (
|
|
11576
|
+
id TEXT PRIMARY KEY,
|
|
11577
|
+
work_id TEXT NOT NULL,
|
|
11578
|
+
work_scope TEXT NOT NULL CHECK (work_scope IN ('source-owned', 'synthetic-test')),
|
|
11579
|
+
source TEXT,
|
|
11580
|
+
account_id TEXT,
|
|
11581
|
+
event_id TEXT,
|
|
11582
|
+
rule_id TEXT,
|
|
11583
|
+
rule_version INTEGER CHECK (rule_version > 0),
|
|
11584
|
+
judge_id TEXT NOT NULL,
|
|
11585
|
+
judge_version INTEGER NOT NULL CHECK (judge_version > 0),
|
|
11586
|
+
judge_kind TEXT NOT NULL CHECK (judge_kind IN (${JUDGE_KIND_SQL_VALUES})),
|
|
11587
|
+
test_work_id TEXT,
|
|
11588
|
+
budget_version INTEGER NOT NULL REFERENCES judge_budget_versions(version),
|
|
11589
|
+
provider TEXT,
|
|
11590
|
+
global_ledger_key TEXT NOT NULL CHECK (global_ledger_key = 'installation'),
|
|
11591
|
+
state TEXT NOT NULL CHECK (state IN ('reserved', 'in-flight', 'settled', 'released')),
|
|
11592
|
+
estimated_input_tokens INTEGER NOT NULL CHECK (estimated_input_tokens >= 0),
|
|
11593
|
+
actual_input_tokens INTEGER CHECK (actual_input_tokens >= 0),
|
|
11594
|
+
concurrency_owned INTEGER NOT NULL CHECK (concurrency_owned IN (0, 1)),
|
|
11595
|
+
reserved_at INTEGER NOT NULL,
|
|
11596
|
+
in_flight_at INTEGER,
|
|
11597
|
+
settled_at INTEGER,
|
|
11598
|
+
released_at INTEGER,
|
|
11599
|
+
switch_generation INTEGER NOT NULL CHECK (switch_generation >= 0),
|
|
11600
|
+
test_authority_rule_id TEXT,
|
|
11601
|
+
test_authority_rule_version INTEGER CHECK (test_authority_rule_version > 0),
|
|
11602
|
+
test_authority_activation_id TEXT,
|
|
11603
|
+
test_authority_approval_id TEXT,
|
|
11604
|
+
test_authority_challenge_hash TEXT CHECK (
|
|
11605
|
+
test_authority_challenge_hash IS NULL
|
|
11606
|
+
OR (length(test_authority_challenge_hash) = 64
|
|
11607
|
+
AND test_authority_challenge_hash NOT GLOB '*[^0-9a-f]*')
|
|
11608
|
+
),
|
|
11609
|
+
test_authority_challenge_origin TEXT CHECK (test_authority_challenge_origin IN ('terminal', 'view')),
|
|
11610
|
+
test_authority_challenge_expires_at INTEGER,
|
|
11611
|
+
FOREIGN KEY (rule_id, rule_version) REFERENCES rule_versions(rule_id, version),
|
|
11612
|
+
FOREIGN KEY (judge_id, judge_version) REFERENCES judge_versions(judge_id, version),
|
|
11613
|
+
CHECK (
|
|
11614
|
+
(work_scope = 'source-owned'
|
|
11615
|
+
AND source IS NOT NULL
|
|
11616
|
+
AND source IN ('gmail', 'slack', 'resend', 'whatsapp')
|
|
11617
|
+
AND account_id IS NOT NULL
|
|
11618
|
+
AND event_id IS NOT NULL
|
|
11619
|
+
AND rule_id IS NOT NULL
|
|
11620
|
+
AND rule_version IS NOT NULL
|
|
11621
|
+
AND test_work_id IS NULL
|
|
11622
|
+
AND provider IS NOT NULL)
|
|
11623
|
+
OR
|
|
11624
|
+
(work_scope = 'synthetic-test'
|
|
11625
|
+
AND source IS NULL
|
|
11626
|
+
AND account_id IS NULL
|
|
11627
|
+
AND event_id IS NULL
|
|
11628
|
+
AND rule_id IS NULL
|
|
11629
|
+
AND rule_version IS NULL
|
|
11630
|
+
AND test_work_id IS NOT NULL
|
|
11631
|
+
AND provider IS NULL)
|
|
11632
|
+
)
|
|
11633
|
+
) STRICT`,
|
|
11634
|
+
`INSERT INTO judge_budget_reservations_v22
|
|
11635
|
+
(id, work_id, work_scope, source, account_id, event_id, rule_id, rule_version, judge_id, judge_version,
|
|
11636
|
+
judge_kind, test_work_id, budget_version, provider, global_ledger_key, state, estimated_input_tokens,
|
|
11637
|
+
actual_input_tokens, concurrency_owned, reserved_at, in_flight_at, settled_at, released_at, switch_generation,
|
|
11638
|
+
test_authority_rule_id, test_authority_rule_version, test_authority_activation_id, test_authority_approval_id,
|
|
11639
|
+
test_authority_challenge_hash, test_authority_challenge_origin, test_authority_challenge_expires_at)
|
|
11640
|
+
SELECT id, work_id, work_scope, source, account_id, event_id, rule_id, rule_version, judge_id, judge_version,
|
|
11641
|
+
judge_kind, test_work_id, budget_version, provider, global_ledger_key, state, estimated_input_tokens,
|
|
11642
|
+
actual_input_tokens, concurrency_owned, reserved_at, in_flight_at, settled_at, released_at,
|
|
11643
|
+
switch_generation, test_authority_rule_id, test_authority_rule_version, test_authority_activation_id,
|
|
11644
|
+
test_authority_approval_id, test_authority_challenge_hash, test_authority_challenge_origin,
|
|
11645
|
+
test_authority_challenge_expires_at
|
|
11646
|
+
FROM judge_budget_reservations`,
|
|
11647
|
+
"DROP TABLE judge_budget_reservations",
|
|
11648
|
+
"ALTER TABLE judge_budget_reservations_v22 RENAME TO judge_budget_reservations",
|
|
11649
|
+
`CREATE UNIQUE INDEX judge_budget_reservations_live_work_identity
|
|
11650
|
+
ON judge_budget_reservations(work_id)
|
|
11651
|
+
WHERE state IN ('reserved', 'in-flight')`,
|
|
11652
|
+
`CREATE INDEX judge_budget_reservations_rule_rolling
|
|
11653
|
+
ON judge_budget_reservations(rule_id, reserved_at)
|
|
11654
|
+
WHERE work_scope = 'source-owned'`,
|
|
11655
|
+
`CREATE INDEX judge_budget_reservations_provider_rolling
|
|
11656
|
+
ON judge_budget_reservations(provider, reserved_at)
|
|
11657
|
+
WHERE provider IS NOT NULL`,
|
|
11658
|
+
`CREATE INDEX judge_budget_reservations_global_rolling
|
|
11659
|
+
ON judge_budget_reservations(global_ledger_key, reserved_at)`,
|
|
11660
|
+
`CREATE UNIQUE INDEX judge_budget_reservations_source_scope
|
|
11661
|
+
ON judge_budget_reservations(source, account_id, event_id, rule_id, rule_version)
|
|
11662
|
+
WHERE work_scope = 'source-owned' AND state IN ('reserved', 'in-flight')`,
|
|
11663
|
+
`CREATE INDEX judge_budget_reservations_synthetic_scope
|
|
11664
|
+
ON judge_budget_reservations(test_work_id, judge_id, judge_version, state)
|
|
11665
|
+
WHERE work_scope = 'synthetic-test'`,
|
|
11666
|
+
`CREATE INDEX judge_budget_reservations_kind_scope
|
|
11667
|
+
ON judge_budget_reservations(judge_kind, work_scope, state)`,
|
|
11668
|
+
`CREATE INDEX judge_budget_reservations_synthetic_authority
|
|
11669
|
+
ON judge_budget_reservations(test_authority_rule_id, test_authority_rule_version, test_authority_activation_id,
|
|
11670
|
+
test_authority_approval_id, test_authority_challenge_hash)
|
|
11671
|
+
WHERE work_scope = 'synthetic-test'`,
|
|
11672
|
+
`CREATE TRIGGER judge_budget_reservations_state_machine
|
|
11673
|
+
BEFORE UPDATE OF state ON judge_budget_reservations
|
|
11674
|
+
WHEN NEW.state <> OLD.state AND NOT (
|
|
11675
|
+
(OLD.state = 'reserved' AND NEW.state IN ('in-flight', 'released'))
|
|
11676
|
+
OR (OLD.state = 'in-flight' AND NEW.state = 'settled')
|
|
11677
|
+
)
|
|
11678
|
+
BEGIN
|
|
11679
|
+
SELECT RAISE(ABORT, 'invalid judge reservation state transition');
|
|
11680
|
+
END`,
|
|
11681
|
+
"UPDATE meta SET value = '22' WHERE key = 'schema_version'"
|
|
11682
|
+
]
|
|
11471
11683
|
}
|
|
11472
11684
|
];
|
|
11473
11685
|
function initialiseLedger(database) {
|
|
@@ -11477,12 +11689,32 @@ function initialiseLedger(database) {
|
|
|
11477
11689
|
applied_at INTEGER NOT NULL
|
|
11478
11690
|
) STRICT`);
|
|
11479
11691
|
}
|
|
11692
|
+
/** The highest schema version a database's ledger records, or 0 when it has no ledger yet. It only reads. */
|
|
11693
|
+
function recordedSchemaVersion(database) {
|
|
11694
|
+
if (database.prepare("SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get() === void 0) return 0;
|
|
11695
|
+
return database.prepare("SELECT MAX(version) AS version FROM schema_migrations").get().version ?? 0;
|
|
11696
|
+
}
|
|
11697
|
+
/** Refuses a recorded schema version newer than this binary's, naming both and nothing else. */
|
|
11698
|
+
function refuseNewerVersion(databaseVersion, migrations = EVENT_MIGRATIONS) {
|
|
11699
|
+
const binaryVersion = migrations.at(-1)?.version ?? 0;
|
|
11700
|
+
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`);
|
|
11701
|
+
}
|
|
11702
|
+
/**
|
|
11703
|
+
* Refuses a database a newer agent-events has migrated, by reading its ledger and nothing else: no table is created and
|
|
11704
|
+
* nothing is written, so it runs before the opener sets a journal mode or this file creates its ledger. A database
|
|
11705
|
+
* with no ledger yet is new, and passes.
|
|
11706
|
+
*/
|
|
11707
|
+
function refuseNewerSchema(database, migrations = EVENT_MIGRATIONS) {
|
|
11708
|
+
refuseNewerVersion(recordedSchemaVersion(database), migrations);
|
|
11709
|
+
}
|
|
11480
11710
|
/** Applies ordered daemon-owned migrations, one BEGIN IMMEDIATE transaction per version. */
|
|
11481
11711
|
function applyMigrations(database, migrations = EVENT_MIGRATIONS, hooks = {}) {
|
|
11712
|
+
refuseNewerSchema(database, migrations);
|
|
11482
11713
|
initialiseLedger(database);
|
|
11714
|
+
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
11715
|
const applied = database.prepare("SELECT version, name FROM schema_migrations ORDER BY version").all();
|
|
11484
|
-
|
|
11485
|
-
|
|
11716
|
+
refuseNewerVersion(applied.at(-1)?.version ?? 0, migrations);
|
|
11717
|
+
for (const migration of migrations) {
|
|
11486
11718
|
const recorded = applied.find((entry) => entry.version === migration.version);
|
|
11487
11719
|
if (recorded) {
|
|
11488
11720
|
if (recorded.name !== migration.name) throw new Error(`event migration ${migration.version} does not match its ledger name`);
|
|
@@ -11517,18 +11749,44 @@ function installationIdFor(database) {
|
|
|
11517
11749
|
database.prepare("INSERT INTO meta (key, value) VALUES (?, ?)").run("installation_id", created);
|
|
11518
11750
|
return created;
|
|
11519
11751
|
}
|
|
11752
|
+
/** SQLite's primary result code for a read that has to write first: recovering what a crash interrupted. */
|
|
11753
|
+
const SQLITE_READONLY = 8;
|
|
11754
|
+
/**
|
|
11755
|
+
* The schema version a database records, read through a read-only connection, so that a database a newer agent-events
|
|
11756
|
+
* migrated is refused without this release ever opening it for writing. It waits out a lock as the writable opener
|
|
11757
|
+
* would. Null only when SQLite must first recover an interrupted transaction, which takes a writable connection: the
|
|
11758
|
+
* opener checks again there. Any other failure, a lock held past the wait among them, refuses the open as it is.
|
|
11759
|
+
*/
|
|
11760
|
+
function recordedVersionReadOnly(Database, path, busyTimeoutMs = 5e3) {
|
|
11761
|
+
try {
|
|
11762
|
+
const probe = new Database(path, { readOnly: true });
|
|
11763
|
+
try {
|
|
11764
|
+
probe.exec(`PRAGMA busy_timeout = ${Math.trunc(busyTimeoutMs)}`);
|
|
11765
|
+
return recordedSchemaVersion(probe);
|
|
11766
|
+
} finally {
|
|
11767
|
+
probe.close();
|
|
11768
|
+
}
|
|
11769
|
+
} catch (error) {
|
|
11770
|
+
const code = error.errcode;
|
|
11771
|
+
if (typeof code === "number" && (code & 255) === SQLITE_READONLY) return null;
|
|
11772
|
+
throw error;
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11520
11775
|
/** Opens the daemon’s one local authority after the Task 4 owner-only path checks have succeeded. */
|
|
11521
11776
|
async function openEventDatabase(options) {
|
|
11522
11777
|
const { DatabaseSync: Database } = await loadSqlite();
|
|
11523
11778
|
const paths = await ensureEventPaths(eventPaths(options.stateDir));
|
|
11524
11779
|
await ensureOwnerOnlyFile(paths.database);
|
|
11780
|
+
const recorded = recordedVersionReadOnly(Database, paths.database);
|
|
11781
|
+
if (recorded !== null) refuseNewerVersion(recorded);
|
|
11525
11782
|
const database = new Database(paths.database);
|
|
11526
11783
|
try {
|
|
11784
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
11785
|
+
refuseNewerSchema(database);
|
|
11527
11786
|
database.exec("PRAGMA foreign_keys = ON");
|
|
11528
11787
|
database.exec("PRAGMA journal_mode = WAL");
|
|
11529
11788
|
database.exec("PRAGMA synchronous = FULL");
|
|
11530
11789
|
database.exec("PRAGMA secure_delete = ON");
|
|
11531
|
-
database.exec("PRAGMA busy_timeout = 5000");
|
|
11532
11790
|
applyMigrations(database);
|
|
11533
11791
|
return {
|
|
11534
11792
|
database,
|
|
@@ -11841,16 +12099,20 @@ var ActivationRuntime = class {
|
|
|
11841
12099
|
}
|
|
11842
12100
|
/** A narrowing revocation is allowed to win over a claimed completion, but no other pointer mutation is. */
|
|
11843
12101
|
async cancelForRevocation(input) {
|
|
12102
|
+
this.#store.immediate(() => {
|
|
12103
|
+
this.cancelForRevocationInTransaction(input);
|
|
12104
|
+
});
|
|
12105
|
+
}
|
|
12106
|
+
/** A caller already holding the removal transaction cancels every matching intent before revocation commits. */
|
|
12107
|
+
cancelForRevocationInTransaction(input) {
|
|
11844
12108
|
const rows = this.#store.database.prepare("SELECT id, document FROM activation_intents WHERE status IN ('pending', 'pending-completion') ORDER BY id").all();
|
|
11845
|
-
|
|
12109
|
+
let cancelled = 0;
|
|
11846
12110
|
for (const row of rows) {
|
|
11847
12111
|
if (!this.#isRevokedBinding(JSON.parse(row.document), input)) continue;
|
|
11848
|
-
|
|
12112
|
+
this.#cancel(row.id, ACTIVATION_FAILURE.AUTHORIZATION_REVOKED);
|
|
12113
|
+
cancelled += 1;
|
|
11849
12114
|
}
|
|
11850
|
-
|
|
11851
|
-
this.#store.immediate(() => {
|
|
11852
|
-
for (const intentId of cancelled) this.#cancel(intentId, ACTIVATION_FAILURE.AUTHORIZATION_REVOKED);
|
|
11853
|
-
});
|
|
12115
|
+
return cancelled;
|
|
11854
12116
|
}
|
|
11855
12117
|
/** Called by the kind-disable transaction; no pending kind approval may recreate the pointer after disable wins. */
|
|
11856
12118
|
cancelForJudgeKindInTransaction(kind) {
|
|
@@ -11858,7 +12120,7 @@ var ActivationRuntime = class {
|
|
|
11858
12120
|
let cancelled = 0;
|
|
11859
12121
|
for (const row of rows) {
|
|
11860
12122
|
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) {
|
|
12123
|
+
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
12124
|
this.#cancel(row.id, ACTIVATION_FAILURE.AUTHORIZATION_REVOKED);
|
|
11863
12125
|
cancelled += 1;
|
|
11864
12126
|
}
|
|
@@ -11976,6 +12238,8 @@ var ActivationRuntime = class {
|
|
|
11976
12238
|
const now = this.#now();
|
|
11977
12239
|
const plannedAccounts = [...new Map(points.map((point) => [`${point.source}\u0000${point.accountId}`, point])).values()];
|
|
11978
12240
|
this.#store.immediate(() => {
|
|
12241
|
+
this.#assertBoundRuleAuthority(document);
|
|
12242
|
+
this.#assertPreparationPlanLive(document, effect, replacementOfVersion);
|
|
11979
12243
|
this.#assertJudgeActivationLive(document, effect);
|
|
11980
12244
|
const latestRevocation = plannedAccounts.reduce((latest, point) => {
|
|
11981
12245
|
const row = this.#store.database.prepare(`SELECT MAX(revoked_at) AS revoked_at FROM account_revocations
|
|
@@ -11988,9 +12252,28 @@ var ActivationRuntime = class {
|
|
|
11988
12252
|
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
12253
|
});
|
|
11990
12254
|
const approval = await this.#approvals.createDisclosure(binding);
|
|
12255
|
+
let settled;
|
|
11991
12256
|
this.#store.immediate(() => {
|
|
11992
|
-
|
|
12257
|
+
const latest = this.#intent(intentId);
|
|
12258
|
+
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;
|
|
12259
|
+
if (latest.status !== "pending") {
|
|
12260
|
+
if (linkApproval() !== 1) throw new CommsError("APPROVAL_VOID", "the cancelled activation could not retain its disclosure link");
|
|
12261
|
+
settled = new CommsError("APPROVAL_VOID", "the activation was cancelled before its approval could be linked");
|
|
12262
|
+
return;
|
|
12263
|
+
}
|
|
12264
|
+
try {
|
|
12265
|
+
this.#assertBoundRuleAuthority(document);
|
|
12266
|
+
this.#assertPreparationPlanLive(document, effect, replacementOfVersion);
|
|
12267
|
+
this.#assertJudgeActivationLive(document, effect);
|
|
12268
|
+
} catch (error) {
|
|
12269
|
+
if (linkApproval() !== 1) throw new CommsError("APPROVAL_VOID", "the stale activation could not retain its disclosure link");
|
|
12270
|
+
this.#cancel(intentId, ACTIVATION_FAILURE.APPROVAL_BINDING_DRIFT);
|
|
12271
|
+
settled = error;
|
|
12272
|
+
return;
|
|
12273
|
+
}
|
|
12274
|
+
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
12275
|
});
|
|
12276
|
+
if (settled !== void 0) throw settled;
|
|
11994
12277
|
return {
|
|
11995
12278
|
intentId,
|
|
11996
12279
|
approvalId: approval.approvalId,
|
|
@@ -12013,7 +12296,7 @@ var ActivationRuntime = class {
|
|
|
12013
12296
|
if (allowIssuingChallengeResidue) this.#assertApprovalIssuingChallengeResidue(intent.approval_id);
|
|
12014
12297
|
if (row.status === "cancelled") throw new CommsError("APPROVAL_VOID", "the activation was cancelled before completion");
|
|
12015
12298
|
const document = this.#document(row);
|
|
12016
|
-
this.#
|
|
12299
|
+
this.#assertBoundRuleAuthority(document);
|
|
12017
12300
|
if (document.kind === "judge-budget" || document.kind === "judge-kind") try {
|
|
12018
12301
|
this.#assertJudgeActivationLive(document, JSON.parse(row.effect));
|
|
12019
12302
|
} catch (_error) {
|
|
@@ -12166,7 +12449,7 @@ var ActivationRuntime = class {
|
|
|
12166
12449
|
settled = new CommsError("APPROVAL_VOID", "the activation completion deadline has passed");
|
|
12167
12450
|
return;
|
|
12168
12451
|
}
|
|
12169
|
-
this.#
|
|
12452
|
+
this.#assertBoundRuleAuthority(document);
|
|
12170
12453
|
const effect = JSON.parse(latest.effect);
|
|
12171
12454
|
const settings = this.#switch();
|
|
12172
12455
|
if ((document.kind === "rule" || document.kind === "enable-all") && settings.generation !== effect.switchGeneration) {
|
|
@@ -12280,12 +12563,10 @@ var ActivationRuntime = class {
|
|
|
12280
12563
|
const document = this.#document(intent);
|
|
12281
12564
|
const binding = disclosureBindingFor(intent.id, document);
|
|
12282
12565
|
if (binding.digest !== intent.digest || activationDocumentDigest(document) !== intent.digest) throw new CommsError("APPROVAL_VOID", "the activation document changed after preparation");
|
|
12566
|
+
this.#assertBoundRuleAuthority(document);
|
|
12283
12567
|
if (document.kind === "rule") {
|
|
12284
|
-
const
|
|
12285
|
-
const active = versions.activeVersion("rule", document.rule.ruleId);
|
|
12568
|
+
const active = new ImmutableVersions(this.#store.database).activeVersion("rule", document.rule.ruleId);
|
|
12286
12569
|
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
12570
|
} else if (document.kind === "enable-all") {
|
|
12290
12571
|
const current = this.#switch();
|
|
12291
12572
|
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 +12600,48 @@ var ActivationRuntime = class {
|
|
|
12319
12600
|
#judgeKindDisableGeneration(kind) {
|
|
12320
12601
|
return this.#store.database.prepare("SELECT generation FROM judge_kind_disable_generations WHERE kind = ?").get(kind)?.generation ?? 0;
|
|
12321
12602
|
}
|
|
12322
|
-
/**
|
|
12323
|
-
#
|
|
12324
|
-
if (document.kind !== "rule"
|
|
12325
|
-
const kind = document.rule.agentic.judge.kind;
|
|
12603
|
+
/** Re-read every exact bound rule, including its embedded judge revocation and kind pointer, at each authority gate. */
|
|
12604
|
+
#assertBoundRuleAuthority(document) {
|
|
12605
|
+
if (document.kind !== "rule" && document.kind !== "enable-all") return;
|
|
12326
12606
|
const versions = new ImmutableVersions(this.#store.database);
|
|
12327
|
-
|
|
12607
|
+
if (document.kind === "rule") {
|
|
12608
|
+
const live = versions.prepareRule(document.rule.ruleId, document.rule.version);
|
|
12609
|
+
if (canonicalJson(live) !== canonicalJson(document)) throw new CommsError("APPROVAL_VOID", "the rule changed after preparation");
|
|
12610
|
+
return;
|
|
12611
|
+
}
|
|
12612
|
+
for (const entry of document.ruleVersions) versions.prepareRule(entry.ruleId, entry.ruleVersion);
|
|
12613
|
+
}
|
|
12614
|
+
/** Preparation may await local stores; the exact pointer plan captured before those awaits must still be current. */
|
|
12615
|
+
#assertPreparationPlanLive(document, effect, replacementOfVersion) {
|
|
12616
|
+
if (document.kind === "rule") {
|
|
12617
|
+
const active = new ImmutableVersions(this.#store.database).activeVersion("rule", document.rule.ruleId);
|
|
12618
|
+
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");
|
|
12619
|
+
return;
|
|
12620
|
+
}
|
|
12621
|
+
if (document.kind !== "enable-all") return;
|
|
12622
|
+
const settings = this.#switch();
|
|
12623
|
+
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();
|
|
12624
|
+
if (settings.enabled || settings.generation !== document.switchGeneration || settings.generation !== effect.switchGeneration || canonicalJson(live.map((row) => ({
|
|
12625
|
+
ruleId: row.object_id,
|
|
12626
|
+
ruleVersion: row.version
|
|
12627
|
+
}))) !== canonicalJson(document.ruleVersions) || canonicalJson(live.map((row) => ({
|
|
12628
|
+
ruleId: row.object_id,
|
|
12629
|
+
ruleVersion: row.version,
|
|
12630
|
+
cutoverId: row.current_cutover_id
|
|
12631
|
+
}))) !== canonicalJson(effect.currentCutovers ?? [])) throw new CommsError("APPROVAL_VOID", "the global enablement pointer plan changed during preparation");
|
|
12632
|
+
}
|
|
12633
|
+
/** A damaged captured rule row cannot prove that a kind disable is unrelated, so cancellation is fail-closed. */
|
|
12634
|
+
#ruleVersionsBindJudgeKind(entries, kind) {
|
|
12635
|
+
for (const entry of entries) {
|
|
12636
|
+
const row = this.#store.database.prepare("SELECT document FROM rule_versions WHERE rule_id = ? AND version = ?").get(entry.ruleId, entry.ruleVersion);
|
|
12637
|
+
if (row === void 0) return true;
|
|
12638
|
+
try {
|
|
12639
|
+
if (canonicalFullRuleDocument(JSON.parse(row.document)).agentic?.judge.kind === kind) return true;
|
|
12640
|
+
} catch {
|
|
12641
|
+
return true;
|
|
12642
|
+
}
|
|
12643
|
+
}
|
|
12644
|
+
return false;
|
|
12328
12645
|
}
|
|
12329
12646
|
#document(intent) {
|
|
12330
12647
|
return JSON.parse(intent.document);
|
|
@@ -13583,10 +13900,10 @@ var B3OperatorControl = class {
|
|
|
13583
13900
|
}).disable(kind);
|
|
13584
13901
|
}
|
|
13585
13902
|
async removeJudge(judgeId) {
|
|
13586
|
-
await this.#activations?.cancelForRevocation({ judgeId });
|
|
13587
13903
|
return this.#store.immediate(() => {
|
|
13588
13904
|
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
13905
|
if (rows.length === 0) throw new CommsError("NOT_FOUND", "the judge has no live version to remove");
|
|
13906
|
+
this.#activations?.cancelForRevocationInTransaction({ judgeId });
|
|
13590
13907
|
const revokedAt = this.#now();
|
|
13591
13908
|
const removedVersions = new Set(rows.map((row) => row.version));
|
|
13592
13909
|
for (const row of rows) {
|
|
@@ -14246,7 +14563,8 @@ var B3TargetTestControl = class {
|
|
|
14246
14563
|
SELECT 1 FROM object_revocations AS revoked
|
|
14247
14564
|
WHERE revoked.kind = 'target' AND revoked.object_id = target.target_id AND revoked.version = target.version
|
|
14248
14565
|
)`).get(targetId, targetVersion);
|
|
14249
|
-
if (row === void 0
|
|
14566
|
+
if (row === void 0) return refuse("the exact target version is not live");
|
|
14567
|
+
if (row.enabled !== 1) return refuse("collection is switched off: switch it on with enable-all first, or approve without the test");
|
|
14250
14568
|
if (selectorKind === "active") {
|
|
14251
14569
|
if (this.#store.database.prepare(`SELECT 1 AS present
|
|
14252
14570
|
FROM target_version_references AS reference
|
|
@@ -15547,9 +15865,9 @@ var JudgeTransport = class {
|
|
|
15547
15865
|
let enteredIo = false;
|
|
15548
15866
|
try {
|
|
15549
15867
|
await this.#beforeGate(hooks, "dns");
|
|
15550
|
-
this.#finalGate(hooks, "dns");
|
|
15551
|
-
assertBeforeDeadline(deadline, this.#clock);
|
|
15868
|
+
this.#finalGate(hooks, "dns", deadline);
|
|
15552
15869
|
enteredIo = call.judge.kind === JUDGE_KIND_TYPESAFE;
|
|
15870
|
+
assertBeforeDeadline(deadline, this.#clock);
|
|
15553
15871
|
const connection = await beforeDeadline$1(preparePinnedConnection(call.judge.kind === "typesafe" ? {
|
|
15554
15872
|
url: endpoint,
|
|
15555
15873
|
addressMode: "global-only",
|
|
@@ -15562,9 +15880,9 @@ var JudgeTransport = class {
|
|
|
15562
15880
|
}), deadline, this.#clock);
|
|
15563
15881
|
assertBeforeDeadline(deadline, this.#clock);
|
|
15564
15882
|
await this.#beforeGate(hooks, "tcp");
|
|
15565
|
-
this.#finalGate(hooks, "tcp");
|
|
15566
|
-
assertBeforeDeadline(deadline, this.#clock);
|
|
15883
|
+
this.#finalGate(hooks, "tcp", deadline);
|
|
15567
15884
|
enteredIo = true;
|
|
15885
|
+
assertBeforeDeadline(deadline, this.#clock);
|
|
15568
15886
|
const tcp = await beforeDeadline$1(this.#tcpConnect({
|
|
15569
15887
|
host: connection.address,
|
|
15570
15888
|
port: connection.port
|
|
@@ -15573,7 +15891,7 @@ var JudgeTransport = class {
|
|
|
15573
15891
|
socket = tcp;
|
|
15574
15892
|
if (connection.useTls) {
|
|
15575
15893
|
await this.#beforeGate(hooks, "tls");
|
|
15576
|
-
this.#finalGate(hooks, "tls");
|
|
15894
|
+
this.#finalGate(hooks, "tls", deadline);
|
|
15577
15895
|
assertBeforeDeadline(deadline, this.#clock);
|
|
15578
15896
|
socket = await beforeDeadline$1(this.#tlsConnect({
|
|
15579
15897
|
socket: tcp,
|
|
@@ -15582,7 +15900,7 @@ var JudgeTransport = class {
|
|
|
15582
15900
|
assertBeforeDeadline(deadline, this.#clock);
|
|
15583
15901
|
}
|
|
15584
15902
|
await this.#beforeGate(hooks, "write");
|
|
15585
|
-
this.#finalGate(hooks, "write");
|
|
15903
|
+
this.#finalGate(hooks, "write", deadline);
|
|
15586
15904
|
assertBeforeDeadline(deadline, this.#clock);
|
|
15587
15905
|
socket.write(request);
|
|
15588
15906
|
const body = await beforeDeadline$1(boundedResponse(socket), deadline, this.#clock);
|
|
@@ -15613,14 +15931,16 @@ var JudgeTransport = class {
|
|
|
15613
15931
|
}
|
|
15614
15932
|
}
|
|
15615
15933
|
/**
|
|
15616
|
-
*
|
|
15617
|
-
*
|
|
15934
|
+
* The deadline is checked after the awaited preparation and again inside the durable production gate immediately
|
|
15935
|
+
* before its state transition. No await may be inserted between this gate and the physical B2 network action.
|
|
15618
15936
|
*/
|
|
15619
|
-
#finalGate(hooks, phase) {
|
|
15937
|
+
#finalGate(hooks, phase, deadline) {
|
|
15938
|
+
assertBeforeDeadline(deadline, this.#clock);
|
|
15620
15939
|
let decision;
|
|
15621
15940
|
try {
|
|
15622
|
-
decision = hooks.finalGate(phase);
|
|
15623
|
-
} catch {
|
|
15941
|
+
decision = hooks.finalGate(phase, () => assertBeforeDeadline(deadline, this.#clock));
|
|
15942
|
+
} catch (error) {
|
|
15943
|
+
if (error instanceof DeadlineExceeded) throw error;
|
|
15624
15944
|
throw new FenceRefused();
|
|
15625
15945
|
}
|
|
15626
15946
|
if (decision !== void 0) {
|
|
@@ -15798,33 +16118,19 @@ var JudgeTestControl = class {
|
|
|
15798
16118
|
} catch (error) {
|
|
15799
16119
|
return refusal(input.judgeId, input.version, error);
|
|
15800
16120
|
}
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
}
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
} catch (error) {
|
|
15813
|
-
return refusal(input.judgeId, input.version, error);
|
|
15814
|
-
}
|
|
16121
|
+
const ioGate = new JudgeReservationIoGate({
|
|
16122
|
+
store: this.#store,
|
|
16123
|
+
reservationId: reservation.id,
|
|
16124
|
+
judgeKind: authority.judge.kind,
|
|
16125
|
+
now: this.#now,
|
|
16126
|
+
...prepared.hostedKeyGeneration === null ? {} : { hostedKey: {
|
|
16127
|
+
judgeId: authority.judge.judgeId,
|
|
16128
|
+
judgeVersion: authority.judge.version,
|
|
16129
|
+
generation: prepared.hostedKeyGeneration
|
|
16130
|
+
} }
|
|
16131
|
+
});
|
|
15815
16132
|
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
|
-
},
|
|
16133
|
+
finalGate: ioGate.finalGate,
|
|
15828
16134
|
preparedRequest: prepared
|
|
15829
16135
|
});
|
|
15830
16136
|
if (result.kind === "answer") {
|
|
@@ -15844,27 +16150,13 @@ var JudgeTestControl = class {
|
|
|
15844
16150
|
reservationId: reservation.id
|
|
15845
16151
|
};
|
|
15846
16152
|
}
|
|
15847
|
-
|
|
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({
|
|
16153
|
+
finalizeJudgeTransportRefusal({
|
|
15863
16154
|
store: this.#store,
|
|
15864
16155
|
reservationId: reservation.id,
|
|
15865
16156
|
result: result.code === "OUTPUT_TOKEN_LIMIT_EXCEEDED" ? "output-token-limit" : result.code === "NETWORK_DEADLINE_EXCEEDED" ? "timeout" : "transport-failure",
|
|
15866
16157
|
now: this.#now()
|
|
15867
16158
|
});
|
|
16159
|
+
if (ioGate.firstGateError !== void 0) return refusal(input.judgeId, input.version, ioGate.firstGateError);
|
|
15868
16160
|
return {
|
|
15869
16161
|
kind: "deferred",
|
|
15870
16162
|
judgeId: input.judgeId,
|
|
@@ -16586,61 +16878,44 @@ var JudgeCoordinator = class {
|
|
|
16586
16878
|
reservationId: null
|
|
16587
16879
|
};
|
|
16588
16880
|
}
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
}
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
}
|
|
16599
|
-
}
|
|
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
|
-
}
|
|
16881
|
+
const ioGate = new JudgeReservationIoGate({
|
|
16882
|
+
store: this.#store,
|
|
16883
|
+
reservationId: reservation.id,
|
|
16884
|
+
judgeKind: agentic.judge.kind,
|
|
16885
|
+
now: this.#now,
|
|
16886
|
+
...prepared.hostedKeyGeneration === null ? {} : { hostedKey: {
|
|
16887
|
+
judgeId: agentic.judge.judgeId,
|
|
16888
|
+
judgeVersion: agentic.judge.version,
|
|
16889
|
+
generation: prepared.hostedKeyGeneration
|
|
16890
|
+
} }
|
|
16891
|
+
});
|
|
16610
16892
|
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
|
-
},
|
|
16893
|
+
finalGate: ioGate.finalGate,
|
|
16622
16894
|
preparedRequest: prepared
|
|
16623
16895
|
});
|
|
16624
16896
|
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
16897
|
const settlement = result.code === "OUTPUT_TOKEN_LIMIT_EXCEEDED" ? "output-token-limit" : result.code === "NETWORK_DEADLINE_EXCEEDED" ? "timeout" : "unknown";
|
|
16638
|
-
|
|
16898
|
+
const disposition = finalizeJudgeTransportRefusal({
|
|
16639
16899
|
store: this.#store,
|
|
16640
16900
|
reservationId: reservation.id,
|
|
16641
16901
|
result: settlement,
|
|
16642
16902
|
now: this.#now()
|
|
16643
16903
|
});
|
|
16904
|
+
if (ioGate.firstGateError !== void 0) {
|
|
16905
|
+
if (ioGate.firstGateError instanceof JudgeBudgetError && ioGate.firstGateError.code === "JUDGE_PAUSED") return {
|
|
16906
|
+
kind: "deferred",
|
|
16907
|
+
reservationId: reservation.id
|
|
16908
|
+
};
|
|
16909
|
+
return {
|
|
16910
|
+
kind: "closed",
|
|
16911
|
+
reason: ioGate.firstGateError instanceof Error && "code" in ioGate.firstGateError ? String(ioGate.firstGateError.code) : "authority-changed",
|
|
16912
|
+
reservationId: reservation.id
|
|
16913
|
+
};
|
|
16914
|
+
}
|
|
16915
|
+
if (disposition === "released") return {
|
|
16916
|
+
kind: "deferred",
|
|
16917
|
+
reservationId: reservation.id
|
|
16918
|
+
};
|
|
16644
16919
|
return {
|
|
16645
16920
|
kind: "closed",
|
|
16646
16921
|
reason: closedReason(result),
|
|
@@ -19851,7 +20126,7 @@ function sourceOfDeliveryRule(document) {
|
|
|
19851
20126
|
/** Composes every Task 3/4 control behind the authenticated owner; public adapters never reach a runtime class. */
|
|
19852
20127
|
function createOwnerB3ControlService(options) {
|
|
19853
20128
|
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.
|
|
20129
|
+
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
20130
|
const operator = new B3OperatorControl({
|
|
19856
20131
|
store: options.store,
|
|
19857
20132
|
ports: options.ports,
|
|
@@ -20815,7 +21090,7 @@ async function startOwnerWithLock(options, stateDir, paths, lock, started, onDat
|
|
|
20815
21090
|
control = await (options.startControlServer ?? startControlServer)({
|
|
20816
21091
|
endpoint: instance.endpoint,
|
|
20817
21092
|
token,
|
|
20818
|
-
handle: async (request) => handleControl(owner, lifecycle, database, activations, dryrun, sourceRegistry, writerQuiescer, request, ownerB3Service, doctorReport),
|
|
21093
|
+
handle: async (request) => handleControl(owner, lifecycle, database, activations, dryrun, sourceRegistry, writerQuiescer, request, ownerB3Service, doctorReport, () => connectedEventAccounts(core.config)),
|
|
20819
21094
|
verifyEndpoint: () => verifyPrivateSocketDirectory(paths)
|
|
20820
21095
|
});
|
|
20821
21096
|
if (options.startControlServer === void 0) {
|
|
@@ -20846,7 +21121,7 @@ async function runEventOwner(options = {}) {
|
|
|
20846
21121
|
await owner.stop();
|
|
20847
21122
|
}
|
|
20848
21123
|
}
|
|
20849
|
-
async function handleControl(owner, lifecycle, database, activations, dispatcher, sourceRegistry, writerQuiescer, request, b3OwnerService, doctorReport) {
|
|
21124
|
+
async function handleControl(owner, lifecycle, database, activations, dispatcher, sourceRegistry, writerQuiescer, request, b3OwnerService, doctorReport, connectedAccounts = async () => []) {
|
|
20850
21125
|
if (!isOwnerControlWireOperation(request.operation)) throw new CommsError("USAGE", "the local event control operation is not recognised");
|
|
20851
21126
|
if (isB3ControlOperation(request.operation)) {
|
|
20852
21127
|
if (!b3OwnerService) throw new CommsError("USAGE", "the local B3 control operation is not available");
|
|
@@ -20889,8 +21164,8 @@ async function handleControl(owner, lifecycle, database, activations, dispatcher
|
|
|
20889
21164
|
channel: definition.type.split(".")[0]
|
|
20890
21165
|
};
|
|
20891
21166
|
}
|
|
20892
|
-
case OWNER_CONTROL_WIRES.sourcesList: return sourceRows(database, sourceRegistry);
|
|
20893
|
-
case OWNER_CONTROL_WIRES.sourceShow: return sourceShow(database, sourceRegistry, requiredText(request.args.source, "source"));
|
|
21167
|
+
case OWNER_CONTROL_WIRES.sourcesList: return sourceRows(database, sourceRegistry, await connectedAccounts());
|
|
21168
|
+
case OWNER_CONTROL_WIRES.sourceShow: return sourceShow(database, sourceRegistry, requiredText(request.args.source, "source"), await connectedAccounts());
|
|
20894
21169
|
case OWNER_CONTROL_WIRES.rulesList: return ruleRows(database);
|
|
20895
21170
|
case OWNER_CONTROL_WIRES.ruleShow: return ruleShow(database, requiredText(request.args.ruleId, "rule id"));
|
|
20896
21171
|
case OWNER_CONTROL_WIRES.createRule:
|
|
@@ -20963,7 +21238,12 @@ function targetRows(database) {
|
|
|
20963
21238
|
function createTarget(database, document) {
|
|
20964
21239
|
return domain(() => new ImmutableVersions(database.database).createTarget(document));
|
|
20965
21240
|
}
|
|
20966
|
-
|
|
21241
|
+
/**
|
|
21242
|
+
* Each source's accounts: the scopes active rules read, and every connected account a rule may name, which has no
|
|
21243
|
+
* cursor scope until a rule reads it. Each carries the name it was connected under, so a person, or an agent writing
|
|
21244
|
+
* a first rule, can tell `ibx_…` from `ibx_…` (CUE-474).
|
|
21245
|
+
*/
|
|
21246
|
+
function sourceRows(database, sourceRegistry, connected = []) {
|
|
20967
21247
|
const rows = database.database.prepare("SELECT document FROM rule_versions WHERE state IN ('active', 'superseded')").all();
|
|
20968
21248
|
const scopes = /* @__PURE__ */ new Map();
|
|
20969
21249
|
for (const row of rows) {
|
|
@@ -20985,17 +21265,28 @@ function sourceRows(database, sourceRegistry) {
|
|
|
20985
21265
|
} catch {}
|
|
20986
21266
|
}
|
|
20987
21267
|
}
|
|
20988
|
-
const
|
|
21268
|
+
const nameOf = new Map(connected.map((entry) => [`${entry.source}\u0000${entry.accountId}`, entry.account]));
|
|
21269
|
+
const accounts = [...scopes.values()].map((scope) => ({
|
|
21270
|
+
...scope,
|
|
21271
|
+
account: nameOf.get(`${scope.source}\u0000${scope.accountId}`) ?? null
|
|
21272
|
+
}));
|
|
21273
|
+
for (const entry of connected) if (!accounts.some((row) => row.source === entry.source && row.accountId === entry.accountId)) accounts.push({
|
|
21274
|
+
source: entry.source,
|
|
21275
|
+
accountId: entry.accountId,
|
|
21276
|
+
account: entry.account,
|
|
21277
|
+
cursorScope: null
|
|
21278
|
+
});
|
|
21279
|
+
const ordered = accounts.sort((left, right) => `${left.source}\u0000${left.accountId}\u0000${left.cursorScope ?? ""}`.localeCompare(`${right.source}\u0000${right.accountId}\u0000${right.cursorScope ?? ""}`));
|
|
20989
21280
|
return sourceRegistry.sources().map((source) => ({
|
|
20990
21281
|
source,
|
|
20991
21282
|
accounts: ordered.filter((scope) => scope.source === source)
|
|
20992
21283
|
}));
|
|
20993
21284
|
}
|
|
20994
|
-
function sourceShow(database, sourceRegistry, source) {
|
|
21285
|
+
function sourceShow(database, sourceRegistry, source, connected = []) {
|
|
20995
21286
|
if (!sourceRegistry.sources().includes(source)) throw new CommsError("NOT_FOUND", "the requested source is not configured");
|
|
20996
21287
|
return {
|
|
20997
21288
|
source,
|
|
20998
|
-
accounts: sourceRows(database, sourceRegistry).find((row) => row.source === source).accounts
|
|
21289
|
+
accounts: sourceRows(database, sourceRegistry, connected).find((row) => row.source === source).accounts
|
|
20999
21290
|
};
|
|
21000
21291
|
}
|
|
21001
21292
|
async function acquireWithStaleRecovery(paths) {
|
|
@@ -21143,6 +21434,22 @@ function terminalSafeJson(value) {
|
|
|
21143
21434
|
function renderApprovalViewForTerminal(result) {
|
|
21144
21435
|
return escapeForDisplay(renderB3ApprovalPreviewV1(result).terminal);
|
|
21145
21436
|
}
|
|
21437
|
+
/**
|
|
21438
|
+
* An optional test the owner refused is reported and the approval goes on to its challenge (CUE-475): declining the
|
|
21439
|
+
* test was always allowed, so a test that could not run must not end the approval for the person.
|
|
21440
|
+
*/
|
|
21441
|
+
async function optionalTest(label, test) {
|
|
21442
|
+
try {
|
|
21443
|
+
return await test();
|
|
21444
|
+
} catch (error) {
|
|
21445
|
+
if (!isCommsError(error)) throw error;
|
|
21446
|
+
return {
|
|
21447
|
+
...label,
|
|
21448
|
+
test: "not run",
|
|
21449
|
+
reason: error.message
|
|
21450
|
+
};
|
|
21451
|
+
}
|
|
21452
|
+
}
|
|
21146
21453
|
/** Runs the only interactive terminal approval path; the owner, not this client, made every pending-target selector. */
|
|
21147
21454
|
async function runTerminalApprovalFlow(input) {
|
|
21148
21455
|
const start = await input.start(input.approvalId);
|
|
@@ -21150,13 +21457,14 @@ async function runTerminalApprovalFlow(input) {
|
|
|
21150
21457
|
const selector = start.pendingTargetSelectors[index];
|
|
21151
21458
|
if (selector === void 0) continue;
|
|
21152
21459
|
const answer = await input.terminal.question(`Test pending target ${index + 1} with the fixed synthetic payload before approving? [y/N] `);
|
|
21153
|
-
if (/^\s*(?:y|yes)\s*$/iu.test(answer)) input.print(await input.testPendingTarget(selector));
|
|
21460
|
+
if (/^\s*(?:y|yes)\s*$/iu.test(answer)) input.print(await optionalTest({ pendingTarget: index + 1 }, () => input.testPendingTarget(selector)));
|
|
21154
21461
|
}
|
|
21155
21462
|
for (let index = 0; index < start.pendingRuleJudgeTestSelectors.length; index += 1) {
|
|
21156
21463
|
const selector = start.pendingRuleJudgeTestSelectors[index];
|
|
21157
21464
|
if (selector === void 0) continue;
|
|
21158
21465
|
const answer = await input.terminal.question(`Test pending rule judge ${index + 1} with the fixed synthetic payload before approving? [y/N] `);
|
|
21159
|
-
|
|
21466
|
+
const testPendingRuleJudge = input.testPendingRuleJudge;
|
|
21467
|
+
if (/^\s*(?:y|yes)\s*$/iu.test(answer) && testPendingRuleJudge !== void 0) input.print(await optionalTest({ pendingRuleJudge: index + 1 }, () => testPendingRuleJudge(selector)));
|
|
21160
21468
|
}
|
|
21161
21469
|
const answer = await input.terminal.question(`Type this approval challenge: ${escapeForDisplay(start.challenge)}\n> `);
|
|
21162
21470
|
input.print(await input.approve(input.approvalId, answer));
|
|
@@ -21176,8 +21484,13 @@ async function run(argv, deps = {}) {
|
|
|
21176
21484
|
stdout: process.stdout,
|
|
21177
21485
|
stderr: process.stderr
|
|
21178
21486
|
};
|
|
21487
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-V")) {
|
|
21488
|
+
streams.stdout.write(`${VERSION}\n`);
|
|
21489
|
+
return 0;
|
|
21490
|
+
}
|
|
21179
21491
|
const env = deps.env ?? process.env;
|
|
21180
21492
|
const program = new Command();
|
|
21493
|
+
runEnvironment.set(program, env);
|
|
21181
21494
|
let ran = false;
|
|
21182
21495
|
program.name("agent-events").description("The local event-emission service: one foreground owner and local control clients.").option("--json", "write machine-readable output").option("--no-color", "never colour the output").option("--config-dir <dir>", "pin the configuration directory for this run").option("--state-dir <dir>", "pin the state directory for this run").option("--data-dir <dir>", "pin the data directory for this run").option("--secrets-dir <dir>", "pin the secrets directory for this run").option("--downloads-dir <dir>", "pin the downloads directory for this run").configureOutput({
|
|
21183
21496
|
writeOut: (text) => streams.stdout.write(escapeForDisplay(text)),
|
|
@@ -21610,10 +21923,19 @@ async function run(argv, deps = {}) {
|
|
|
21610
21923
|
return 1;
|
|
21611
21924
|
}
|
|
21612
21925
|
}
|
|
21926
|
+
/**
|
|
21927
|
+
* The state directory a control client looks in: the one pinned for this run, or the one the environment this CLI was
|
|
21928
|
+
* given resolves to. A caller that passes its own `env` to `run` reaches its own owner, never the one the process
|
|
21929
|
+
* environment names.
|
|
21930
|
+
*/
|
|
21613
21931
|
function controlOptions(program) {
|
|
21614
21932
|
const { stateDir } = program.opts();
|
|
21615
|
-
|
|
21933
|
+
if (stateDir !== void 0) return { stateDir };
|
|
21934
|
+
const env = runEnvironment.get(program);
|
|
21935
|
+
return env === void 0 ? {} : { stateDir: resolvePaths({ env }).stateDir };
|
|
21616
21936
|
}
|
|
21937
|
+
/** The environment each program was made with, read by `controlOptions` outside `run`'s closure. */
|
|
21938
|
+
const runEnvironment = /* @__PURE__ */ new WeakMap();
|
|
21617
21939
|
function parseVersion(value) {
|
|
21618
21940
|
const version = Number(value);
|
|
21619
21941
|
if (!Number.isSafeInteger(version) || version < 1) throw new CommsError("USAGE", "rule version must be a positive integer");
|