@humwick/server 0.130.0 → 0.130.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/docs/guides/tools-administration.md +2 -1
- package/docs/openapi.json +1 -1
- package/node_modules/@humwick/kernel/package.json +0 -8
- package/node_modules/@humwick/offload/package.json +0 -4
- package/package.json +5 -5
- package/public/assets/fleet-B_rFEbnL.js +2 -0
- package/public/assets/{index-BK0WV6hF.js → index-BU3uWo2t.js} +4 -4
- package/public/index.html +1 -1
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/scripts/cd-config.ts +16 -3
- package/src/commands-db.ts +7 -3
- package/src/commands-write.ts +8 -0
- package/src/db/activity-write-ops.ts +1 -0
- package/src/db/agent-lifecycle-write-ops.ts +1 -0
- package/src/db/agent-meta.ts +2 -2
- package/src/db/agent-ownership-write-ops.ts +4 -0
- package/src/db/agents-async.ts +6 -2
- package/src/db/agents-write-ops.ts +16 -2
- package/src/db/artifacts-write-ops.ts +8 -3
- package/src/db/bus-outbox-write-ops.ts +1 -0
- package/src/db/channels-write-ops.ts +4 -0
- package/src/db/claude-api-budget-ops.ts +5 -5
- package/src/db/claude-app-oauth.ts +4 -4
- package/src/db/codex-reset-consumes-write-ops.ts +3 -0
- package/src/db/commands-write-ops.ts +21 -0
- package/src/db/config-store-write-ops.ts +2 -0
- package/src/db/continuation-archives-write-ops.ts +2 -0
- package/src/db/delivery-write-ops.ts +5 -0
- package/src/db/hosts-write-ops.ts +14 -0
- package/src/db/inbox-write-ops.ts +4 -0
- package/src/db/insights-write-ops.ts +1 -0
- package/src/db/macos-privacy.ts +1 -1
- package/src/db/message-reads-write-ops.ts +3 -0
- package/src/db/message-write-ops.ts +4 -0
- package/src/db/orchestrators-async.ts +7 -3
- package/src/db/orchestrators-write-ops.ts +7 -1
- package/src/db/orchestrators.ts +17 -5
- package/src/db/prompt-versions-ops.ts +1 -0
- package/src/db/provider-catalog-write-ops.ts +4 -4
- package/src/db/provider-flap-messages-write-ops.ts +1 -0
- package/src/db/provider-quota-lease-write-ops.ts +1 -0
- package/src/db/provider-quota-write-ops.ts +2 -0
- package/src/db/provisioning-write-ops.ts +9 -8
- package/src/db/routing-policy.ts +1 -1
- package/src/db/secrets-broker-write-ops.ts +5 -5
- package/src/db/secrets-vault-write.ts +7 -7
- package/src/db/services-write-ops.ts +3 -0
- package/src/db/spawn-tree-cleanup.ts +3 -0
- package/src/db/team-spawn-delegation.ts +1 -1
- package/src/db/teams-write-ops.ts +4 -4
- package/src/db/timer-cutover-write-ops.ts +7 -0
- package/src/db/timer-ingress-write-ops.ts +9 -0
- package/src/db/timer-operator-write-ops.ts +14 -0
- package/src/db/timer-transfer-write-ops.ts +4 -0
- package/src/db/timer-write-ops.ts +14 -0
- package/src/db/token-db-write-ops.ts +4 -0
- package/src/db/user-settings-write-ops.ts +1 -0
- package/src/db/vault-backup.ts +2 -2
- package/src/db/vault-expiry.ts +2 -2
- package/src/db/vault-health.ts +3 -3
- package/src/db/vault-token-ops.ts +8 -8
- package/src/db/web-tool-call-ops.ts +3 -3
- package/src/db/write-ops.ts +9 -1
- package/src/db/writer-fault-seam.ts +5 -2
- package/src/db/writer-gateway.ts +4 -1
- package/src/mcp-host-tools.ts +1 -1
- package/src/package-pack.ts +42 -39
- package/src/routes/host-resource-control.ts +4 -1
- package/src/routes/orchestrator-bootstrap.ts +2 -2
- package/src/routes/orchestrator.ts +2 -2
- package/src/routes/provider-config.ts +3 -2
- package/src/runtime-tokens.ts +12 -3
- package/src/services/kernel-timers-cutover-chaos-worker.ts +2 -0
- package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -1
- package/public/assets/fleet-BL4UXS_k.js +0 -2
package/public/index.html
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
|
14
14
|
<link rel="manifest" href="manifest.webmanifest">
|
|
15
15
|
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
|
|
16
|
-
<script type="module" crossorigin src="./assets/index-
|
|
16
|
+
<script type="module" crossorigin src="./assets/index-BU3uWo2t.js"></script>
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-QTnfLwEv.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/legacy-storage-carry-C-tBSxRA.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="./assets/dist-DeW2Y1R5.js">
|
package/scripts/cd-config.ts
CHANGED
|
@@ -27,6 +27,19 @@ export function resolveCanonicalRelayDbPath(env: NodeJS.ProcessEnv = process.env
|
|
|
27
27
|
return resolve(env.DB_PATH || join(defaultStateDir(), "agent-relay.db"));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** #3301 — the deploy ledger lives only on the hub. Off the hub a release publishes and stops. */
|
|
31
|
+
export function hasCanonicalDeployLedger(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
32
|
+
return existsSync(resolveCanonicalRelayDbPath(env));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function openCanonicalDeployLedger(): void {
|
|
36
|
+
const path = resolveCanonicalRelayDbPath();
|
|
37
|
+
if (!existsSync(path)) {
|
|
38
|
+
throw new Error(`no deploy ledger at ${path}: deploys run on the hub. CD deploys a published release there, or run \`bun run release --deploy-only\` on the hub.`);
|
|
39
|
+
}
|
|
40
|
+
openExistingDb(path);
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
/**
|
|
31
44
|
* #1763 — the git repository cd-watcher's detached child `git fetch`es against every
|
|
32
45
|
* tick. cd-watcher provisions and owns a DEDICATED clone here, deliberately separate
|
|
@@ -146,7 +159,7 @@ export function withDeployLedgerDb<T>(dbPath: string, fn: () => T): T {
|
|
|
146
159
|
|
|
147
160
|
/** Claim the one live-ledger lease; both the watcher and release.ts use this path. */
|
|
148
161
|
export function claimCanonicalDeployLease(input: { commitSha: string; version: string; hosts: DeployHost[]; initiatedBy: string; attachedEventId?: string; previousSha?: string }): DeployEvent | null {
|
|
149
|
-
|
|
162
|
+
openCanonicalDeployLedger();
|
|
150
163
|
const attached = input.attachedEventId ? getDeployEvent(input.attachedEventId) : undefined;
|
|
151
164
|
if (attached && (attached.status !== "pending" || attached.commitSha !== input.commitSha)) {
|
|
152
165
|
throw new Error(`deploy event ${input.attachedEventId} is not a pending lease for ${input.commitSha}`);
|
|
@@ -193,7 +206,7 @@ export function startDeployLeaseHeartbeat(id: string): ReturnType<typeof setInte
|
|
|
193
206
|
|
|
194
207
|
/** Explicit emergency recovery; normal crash recovery remains the bounded TTL. */
|
|
195
208
|
export function clearCanonicalDeployLease(id: string): boolean {
|
|
196
|
-
|
|
209
|
+
openCanonicalDeployLedger();
|
|
197
210
|
return !!forceFailDeployEvent({ id, reason: "force-cleared by operator via release.ts" });
|
|
198
211
|
}
|
|
199
212
|
|
|
@@ -206,7 +219,7 @@ export function clearCanonicalDeployLease(id: string): boolean {
|
|
|
206
219
|
* the clock, is the authority here).
|
|
207
220
|
*/
|
|
208
221
|
export function markCanonicalDeploySucceeded(id: string): boolean {
|
|
209
|
-
|
|
222
|
+
openCanonicalDeployLedger();
|
|
210
223
|
return !!reconcilePendingDeployEvent({
|
|
211
224
|
id, status: "succeeded", requireLeaseExpired: false,
|
|
212
225
|
reason: "marked succeeded by operator via release.ts (#1532)",
|
package/src/commands-db.ts
CHANGED
|
@@ -814,8 +814,8 @@ export function updateCommand(id: string, input: UpdateCommandInput, opts: Updat
|
|
|
814
814
|
return applyCommandUpdatePostCommit(result.previousStatus, result.command);
|
|
815
815
|
}
|
|
816
816
|
|
|
817
|
-
|
|
818
|
-
|
|
817
|
+
/** Whether the public DELETE may cancel this command. Shared by the sync and async delete paths. */
|
|
818
|
+
export function commandDeletable(existing: Command | null): existing is Command {
|
|
819
819
|
if (!existing) return false;
|
|
820
820
|
if (existing.type === "workspace.merge" && !isLegacyUnfencedWorkspaceMerge(existing)) return false;
|
|
821
821
|
if (!["pending", "accepted"].includes(existing.status)) return false;
|
|
@@ -823,7 +823,11 @@ export function deleteCommand(id: string): boolean {
|
|
|
823
823
|
// cancelable (see updateCommand's post-claim-cancel guard). Report "cannot cancel" honestly rather
|
|
824
824
|
// than a false success: without this the updateCommand guard would no-op the cancel yet we'd still
|
|
825
825
|
// return true, telling the caller a claimed push was canceled when it was not.
|
|
826
|
-
|
|
826
|
+
return !(existing.status === "accepted" && isClaimGatedReviewCommand(existing.type));
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export function deleteCommand(id: string): boolean {
|
|
830
|
+
if (!commandDeletable(getCommand(id))) return false;
|
|
827
831
|
updateCommand(id, { status: "canceled", error: "canceled" });
|
|
828
832
|
return true;
|
|
829
833
|
}
|
package/src/commands-write.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { STALE_TTL_MS } from "./config.ts";
|
|
|
11
11
|
import { bufferedLiveAgentIds } from "./db/presence-buffer.ts";
|
|
12
12
|
import { getGateway } from "./db/gateway.ts";
|
|
13
13
|
import {
|
|
14
|
+
commandDeletable,
|
|
14
15
|
commandUpdateHasPostCommitEffects,
|
|
15
16
|
type CreateCommandOptions,
|
|
16
17
|
type CreateCommandWriteArgs,
|
|
@@ -120,6 +121,13 @@ export async function updateCommandAsync(
|
|
|
120
121
|
return result.command;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/** Public DELETE (#1870 S6a): the cancel is a normal off-loop patch, so it never writes on the main thread. */
|
|
125
|
+
export async function deleteCommandAsync(id: string): Promise<boolean> {
|
|
126
|
+
if (!commandDeletable(await getCommandAsync(id))) return false;
|
|
127
|
+
await updateCommandAsync(id, { status: "canceled", error: "canceled" });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
/** Atomically cancel only if the command is still in the caller's expected live statuses. */
|
|
124
132
|
export function cancelCommandIfStatusAsync(
|
|
125
133
|
args: CancelCommandIfStatusWriteArgs,
|
|
@@ -29,6 +29,7 @@ export const ACTIVITY_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
29
29
|
fn: (args: ActivityCreateOpArgs, ctx: WriterOpContext): ActivityEvent => createActivityEvent(args.input, ctx.now),
|
|
30
30
|
},
|
|
31
31
|
"activity.recordIssueActivity": {
|
|
32
|
+
offLoop: true,
|
|
32
33
|
idempotent: false,
|
|
33
34
|
fn: (args: ActivityRecordIssueOpArgs, ctx: WriterOpContext): ActivityEvent => recordIssueActivity(args.input, ctx.now),
|
|
34
35
|
},
|
|
@@ -66,6 +66,7 @@ export const AGENT_LIFECYCLE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
|
|
|
66
66
|
instanceSuperseded(args.agentId, args.expectedEpoch) ? [] : releaseWorkspaceClaimsForAgent(args.agentId, args.now),
|
|
67
67
|
},
|
|
68
68
|
"agents.seedContinuationObjective": {
|
|
69
|
+
offLoop: true,
|
|
69
70
|
idempotent: true,
|
|
70
71
|
fn: (args: AgentsSeedContinuationOpArgs, ctx: WriterOpContext): null => {
|
|
71
72
|
seedContinuationObjectiveFromSpawnPrompt(args.agentId, args.spawnRequestId, ctx.now);
|
package/src/db/agent-meta.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { mainThreadRegistry } from "./main-thread-state";
|
|
2
1
|
// Agent meta has two storage temperatures: small runtime fields change on status frames, while the
|
|
3
2
|
// launch projection is fixed for the life of a runner instance. Keep the latter out of `agents.meta`
|
|
4
3
|
// so a hot status update never has to carry it through its JSON patch.
|
|
@@ -7,7 +6,8 @@ import { isRecord } from "@humwick/sdk";
|
|
|
7
6
|
|
|
8
7
|
export const AGENT_META_WARN_BYTES = 64 * 1024;
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
// Log dedupe only, so a per-thread copy is correct: the writer thread warns once itself (#1870 S6c).
|
|
10
|
+
const warnedOversizeMeta = new Set<string>();
|
|
11
11
|
|
|
12
12
|
export interface StoredAgentMeta {
|
|
13
13
|
meta: Record<string, unknown>;
|
|
@@ -23,10 +23,12 @@ export interface AgentOwnershipReassignOpArgs { fromUserId: string; toUserId: st
|
|
|
23
23
|
|
|
24
24
|
export const AGENT_OWNERSHIP_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
25
25
|
"agent-ownership.set": {
|
|
26
|
+
offLoop: true,
|
|
26
27
|
idempotent: true,
|
|
27
28
|
fn: (args: AgentOwnershipSetOpArgs, ctx: WriterOpContext): AgentOwnership => setAgentOwnership(args.input, ctx.now),
|
|
28
29
|
},
|
|
29
30
|
"agent-ownership.delete": {
|
|
31
|
+
offLoop: true,
|
|
30
32
|
idempotent: true,
|
|
31
33
|
fn: (args: AgentOwnershipDeleteOpArgs): null => {
|
|
32
34
|
deleteAgentOwnership(args.agentId);
|
|
@@ -36,6 +38,7 @@ export const AGENT_OWNERSHIP_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
|
|
|
36
38
|
// Back-compat migration sweep (#392) — re-running converges (`INSERT ... WHERE NOT EXISTS`), so no
|
|
37
39
|
// args and idempotent: true.
|
|
38
40
|
"agent-ownership.seedDefault": {
|
|
41
|
+
offLoop: true,
|
|
39
42
|
idempotent: true,
|
|
40
43
|
fn: (_args: unknown, ctx: WriterOpContext): null => {
|
|
41
44
|
seedDefaultAgentOwnership(ctx.now);
|
|
@@ -44,6 +47,7 @@ export const AGENT_OWNERSHIP_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> =
|
|
|
44
47
|
},
|
|
45
48
|
// #3567 H4 admin handover — re-applying finds nothing left on the old owner, so it converges.
|
|
46
49
|
"agent-ownership.reassign": {
|
|
50
|
+
offLoop: true,
|
|
47
51
|
idempotent: true,
|
|
48
52
|
fn: (args: AgentOwnershipReassignOpArgs, ctx: WriterOpContext): string[] => reassignAgentOwnership(args.fromUserId, args.toUserId, ctx.now),
|
|
49
53
|
},
|
package/src/db/agents-async.ts
CHANGED
|
@@ -52,8 +52,12 @@ export async function upsertAgentAsync(
|
|
|
52
52
|
): Promise<AgentCard> {
|
|
53
53
|
// #2111 — allocated before the await, per the ordinal-timing rule at the top of setStatusAsync.
|
|
54
54
|
const seq = nextLifecycleSeq();
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
return finishAgentUpsertAsync(input.id, (await getGateway().write("agents.upsert", { input, opts, seq })) as unknown as UpsertAgentTxResult);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The registration's follow-ups after its row committed. Shared with `upsertOrchestratorAsync`. */
|
|
59
|
+
export async function finishAgentUpsertAsync(inputId: string, { agent, contextState }: UpsertAgentTxResult): Promise<AgentCard> {
|
|
60
|
+
if (contextState) await recordContextSnapshotAsync(inputId, contextState);
|
|
57
61
|
evictPendingPresence(agent.id);
|
|
58
62
|
// Both effects settle on the writer. The `spawnRequestId` guard is `seedContinuationObjectiveFromSpawnPrompt`'s
|
|
59
63
|
// own first condition, hoisted here so a registration without one costs no thread hop at all.
|
|
@@ -73,27 +73,32 @@ export interface AgentsRevokeOrphanedTokensOpArgs { graceMs: number; lagGraceMs:
|
|
|
73
73
|
export interface AgentsRevokeTokensForAgentOpArgs { agentId: string; reason?: LineageRevokeReason }
|
|
74
74
|
|
|
75
75
|
export const AGENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
76
|
-
"agents.pruneIdentityArchive": { idempotent: true, fn: (_args: Record<string, never>, ctx: WriterOpContext) => pruneAgentIdentityArchive(ctx.now) },
|
|
77
|
-
"agents.idleSelfCare": { idempotent: false, fn: (args: { id: string; epoch: number }, ctx: WriterOpContext) => applyIdleSelfCare(args, ctx.now) },
|
|
76
|
+
"agents.pruneIdentityArchive": { offLoop: true, idempotent: true, fn: (_args: Record<string, never>, ctx: WriterOpContext) => pruneAgentIdentityArchive(ctx.now) },
|
|
77
|
+
"agents.idleSelfCare": { offLoop: true, idempotent: false, fn: (args: { id: string; epoch: number }, ctx: WriterOpContext) => applyIdleSelfCare(args, ctx.now) },
|
|
78
78
|
"agents.upsert": {
|
|
79
|
+
offLoop: true,
|
|
79
80
|
idempotent: true,
|
|
80
81
|
fn: (args: AgentsUpsertOpArgs, ctx: WriterOpContext): UpsertAgentTxResult =>
|
|
81
82
|
upsertAgentInternal(args.input, args.opts, ctx.now, args.seq),
|
|
82
83
|
},
|
|
83
84
|
"agents.setLabel": {
|
|
85
|
+
offLoop: true,
|
|
84
86
|
idempotent: true,
|
|
85
87
|
fn: (args: AgentsSetLabelOpArgs): boolean => setLabel(args.id, args.label),
|
|
86
88
|
},
|
|
87
89
|
"agents.setKeep": {
|
|
90
|
+
offLoop: true,
|
|
88
91
|
idempotent: true,
|
|
89
92
|
fn: (args: { id: string; keep: AgentKeep | null }): boolean => setAgentKeepState(args.id, args.keep),
|
|
90
93
|
},
|
|
91
94
|
"agents.setTeam": {
|
|
95
|
+
offLoop: true,
|
|
92
96
|
idempotent: true,
|
|
93
97
|
fn: (args: AgentsSetTeamOpArgs, ctx: WriterOpContext): AgentCard | null =>
|
|
94
98
|
setAgentTeam(args.agentId, args.teamId, ctx.now),
|
|
95
99
|
},
|
|
96
100
|
"agents.setTags": {
|
|
101
|
+
offLoop: true,
|
|
97
102
|
idempotent: true,
|
|
98
103
|
fn: (args: AgentsSetTagsOpArgs, ctx: WriterOpContext): AgentCard | null =>
|
|
99
104
|
setTags(args.id, args.tags, ctx.now),
|
|
@@ -115,16 +120,19 @@ export const AGENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.f
|
|
|
115
120
|
markReady(args.id, args.ready, args.guard, ctx.now, args.seq),
|
|
116
121
|
},
|
|
117
122
|
"agents.mergeMeta": {
|
|
123
|
+
offLoop: true,
|
|
118
124
|
idempotent: true,
|
|
119
125
|
fn: (args: AgentsMergeMetaOpArgs, ctx: WriterOpContext): boolean =>
|
|
120
126
|
mergeAgentMeta(args.id, args.meta, args.guard, ctx.now),
|
|
121
127
|
},
|
|
122
128
|
"agents.markReadyNotified": {
|
|
129
|
+
offLoop: true,
|
|
123
130
|
idempotent: true,
|
|
124
131
|
fn: (args: AgentsMarkReadyNotifiedOpArgs, ctx: WriterOpContext): boolean =>
|
|
125
132
|
markAgentReadyNotified(args.id, ctx.now),
|
|
126
133
|
},
|
|
127
134
|
"agents.clearMetaKeys": {
|
|
135
|
+
offLoop: true,
|
|
128
136
|
idempotent: true,
|
|
129
137
|
fn: (args: AgentsClearMetaKeysOpArgs): boolean => clearAgentMetaKeys(args.id, args.keys),
|
|
130
138
|
},
|
|
@@ -135,11 +143,13 @@ export const AGENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.f
|
|
|
135
143
|
recordAgentExitDiagnosticsInternal(args.id, args.diagnostics, ctx.now, args.seq),
|
|
136
144
|
},
|
|
137
145
|
"agents.heartbeat": {
|
|
146
|
+
offLoop: true,
|
|
138
147
|
idempotent: true,
|
|
139
148
|
fn: (args: AgentsHeartbeatOpArgs, ctx: WriterOpContext): boolean =>
|
|
140
149
|
heartbeat(args.id, args.guard, args.runtime, ctx.now),
|
|
141
150
|
},
|
|
142
151
|
"agents.recordContextSnapshot": {
|
|
152
|
+
offLoop: true,
|
|
143
153
|
idempotent: true,
|
|
144
154
|
fn: (args: AgentsRecordContextSnapshotOpArgs, ctx: WriterOpContext): null => {
|
|
145
155
|
recordContextSnapshot(args.agentId, args.context, ctx.now);
|
|
@@ -147,16 +157,19 @@ export const AGENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.f
|
|
|
147
157
|
},
|
|
148
158
|
},
|
|
149
159
|
"agents.reapStale": {
|
|
160
|
+
offLoop: true,
|
|
150
161
|
idempotent: true,
|
|
151
162
|
fn: (args: AgentsReapStaleOpArgs, ctx: WriterOpContext): string[] =>
|
|
152
163
|
reapStaleAgentsInternal(args.ttlMs, args.lagGraceMs, ctx.now, args.protectedIds, args.seq),
|
|
153
164
|
},
|
|
154
165
|
"agents.pruneOffline": {
|
|
166
|
+
offLoop: true,
|
|
155
167
|
idempotent: true,
|
|
156
168
|
fn: (args: AgentsPruneOfflineOpArgs, ctx: WriterOpContext) =>
|
|
157
169
|
pruneOfflineAgentsWithFinality(args, ctx.now),
|
|
158
170
|
},
|
|
159
171
|
"agents.delete": {
|
|
172
|
+
offLoop: true,
|
|
160
173
|
idempotent: true,
|
|
161
174
|
fn: (args: AgentsDeleteOpArgs, ctx: WriterOpContext): boolean => deleteAgentInternal(args.id, ctx.now),
|
|
162
175
|
},
|
|
@@ -167,6 +180,7 @@ export const AGENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.f
|
|
|
167
180
|
revokeOrphanedRuntimeTokens(args.graceMs, args.lagGraceMs, ctx.now),
|
|
168
181
|
},
|
|
169
182
|
"agents.revokeTokensForAgent": {
|
|
183
|
+
offLoop: true,
|
|
170
184
|
idempotent: true,
|
|
171
185
|
fn: (args: AgentsRevokeTokensForAgentOpArgs, ctx: WriterOpContext): string[] =>
|
|
172
186
|
revokeRuntimeTokensForAgent(args.agentId, ctx.now, args.reason),
|
|
@@ -13,6 +13,7 @@ import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
|
|
|
13
13
|
|
|
14
14
|
export const ARTIFACTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
15
15
|
"artifacts.upsertBlob": {
|
|
16
|
+
offLoop: true,
|
|
16
17
|
idempotent: true,
|
|
17
18
|
fn: (args: { input: Parameters<typeof upsertArtifactBlob>[0] }, ctx: WriterOpContext) =>
|
|
18
19
|
upsertArtifactBlob({ ...args.input, createdAt: args.input.createdAt ?? ctx.now }),
|
|
@@ -22,23 +23,27 @@ export const ARTIFACTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Objec
|
|
|
22
23
|
idempotent: false,
|
|
23
24
|
fn: (args: { input: Parameters<typeof createArtifact>[0] }, ctx: WriterOpContext) => createArtifact(args.input, ctx.now),
|
|
24
25
|
},
|
|
25
|
-
"artifacts.pin": { idempotent: true, fn: (args: { id: string; pinned: boolean; readScope?: import("./artifacts").ArtifactReadScope }) => setArtifactPinned(args.id, args.pinned, args.readScope) },
|
|
26
|
-
"artifacts.delete": { idempotent: true, fn: (args: { id: string }) => deleteArtifact(args.id, false) },
|
|
27
|
-
"artifacts.deleteBlob": { idempotent: true, fn: (args: { digest: string }) => deleteArtifactBlob(args.digest) },
|
|
26
|
+
"artifacts.pin": { offLoop: true, idempotent: true, fn: (args: { id: string; pinned: boolean; readScope?: import("./artifacts").ArtifactReadScope }) => setArtifactPinned(args.id, args.pinned, args.readScope) },
|
|
27
|
+
"artifacts.delete": { offLoop: true, idempotent: true, fn: (args: { id: string }) => deleteArtifact(args.id, false) },
|
|
28
|
+
"artifacts.deleteBlob": { offLoop: true, idempotent: true, fn: (args: { digest: string }) => deleteArtifactBlob(args.digest) },
|
|
28
29
|
"artifacts.link": {
|
|
30
|
+
offLoop: true,
|
|
29
31
|
idempotent: true,
|
|
30
32
|
fn: (args: { input: Parameters<typeof linkArtifact>[0] }, ctx: WriterOpContext) => linkArtifact(args.input, ctx.now),
|
|
31
33
|
},
|
|
32
34
|
"artifacts.linkExistingEntity": {
|
|
35
|
+
offLoop: true,
|
|
33
36
|
idempotent: true,
|
|
34
37
|
fn: (args: { input: Parameters<typeof linkArtifactToExistingEntity>[0] }, ctx: WriterOpContext) => linkArtifactToExistingEntity(args.input, ctx.now),
|
|
35
38
|
},
|
|
36
39
|
"artifacts.deleteLinksForEntity": {
|
|
40
|
+
offLoop: true,
|
|
37
41
|
idempotent: true,
|
|
38
42
|
fn: (args: { entityType: Parameters<typeof deleteArtifactLinksForEntity>[0]; entityId: string | number }) =>
|
|
39
43
|
deleteArtifactLinksForEntity(args.entityType, args.entityId),
|
|
40
44
|
},
|
|
41
45
|
"artifacts.sweep": {
|
|
46
|
+
offLoop: true,
|
|
42
47
|
idempotent: true,
|
|
43
48
|
fn: (args: { input?: Parameters<typeof sweepArtifacts>[0] }, ctx: WriterOpContext) =>
|
|
44
49
|
sweepArtifacts({ ...args.input, now: args.input?.now ?? ctx.now }, false),
|
|
@@ -49,6 +49,7 @@ export const BUS_OUTBOX_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Obje
|
|
|
49
49
|
// the same end state (no rows older than the threshold), so it is idempotent and needs no key. `ctx.now`
|
|
50
50
|
// is the submission-time wall-clock (H6).
|
|
51
51
|
"bus.prune": {
|
|
52
|
+
offLoop: true,
|
|
52
53
|
idempotent: true,
|
|
53
54
|
fn: (args: BusPruneOpArgs, ctx: WriterOpContext): { changes: number } => ({
|
|
54
55
|
changes: pruneOutbox(args.retentionMs, ctx.now),
|
|
@@ -14,15 +14,18 @@ import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
|
|
|
14
14
|
|
|
15
15
|
export const CHANNELS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
16
16
|
"channels.deleteBinding": {
|
|
17
|
+
offLoop: true,
|
|
17
18
|
idempotent: true,
|
|
18
19
|
fn: (args: { id: string; fence: ChannelBindingSessionFence }) => deleteChannelBindingBody(args.id, args.fence),
|
|
19
20
|
},
|
|
20
21
|
"channels.upsertBinding": {
|
|
22
|
+
offLoop: true,
|
|
21
23
|
idempotent: true,
|
|
22
24
|
fn: (args: { input: UpsertChannelBindingInput; presence?: PresenceSnapshot; fence?: ChannelBindingSessionFence }, ctx: WriterOpContext) =>
|
|
23
25
|
upsertChannelBindingBody(args.input, ctx.now, args.presence, args.fence),
|
|
24
26
|
},
|
|
25
27
|
"channels.registerAgent": {
|
|
28
|
+
offLoop: true,
|
|
26
29
|
idempotent: true,
|
|
27
30
|
fn: (args: { agentId: string; expectedEpoch: number; presence?: PresenceSnapshot }, ctx: WriterOpContext) => {
|
|
28
31
|
// Re-read under this operation's BEGIN IMMEDIATE. A card captured before an await may now be
|
|
@@ -36,6 +39,7 @@ export const CHANNELS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
36
39
|
},
|
|
37
40
|
},
|
|
38
41
|
"channels.evaluatePoolBindings": {
|
|
42
|
+
offLoop: true,
|
|
39
43
|
idempotent: true,
|
|
40
44
|
fn: (args: { presence?: PresenceSnapshot }, ctx: WriterOpContext) => evaluatePoolBindingsBody(ctx.now, args.presence),
|
|
41
45
|
},
|
|
@@ -7,9 +7,9 @@ export const CLAUDE_API_BUDGET_READ_OPS = {
|
|
|
7
7
|
"apiBudget.status": (args: { now: number; budgetId?: ApiBudgetId }) => apiBudgetStatus(args.now, args.budgetId),
|
|
8
8
|
};
|
|
9
9
|
export const CLAUDE_API_BUDGET_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
|
|
10
|
-
"apiBudget.reconcile": { idempotent: true, fn: (args: { now: number }) => reconcileApiBudgetSessions(args.now) },
|
|
11
|
-
"apiBudget.pace": { idempotent: true, fn: (args: { leadTimeMs: number; now: number; budgetId?: ApiBudgetId }) => apiBudgetPace(args.leadTimeMs, args.now, args.budgetId) },
|
|
12
|
-
"apiBudget.configure": { idempotent: true, fn: (args: { input: ApiBudgetConfig; now: number }) => configureApiBudget(args.input, args.now) },
|
|
13
|
-
"apiBudget.reserve": { idempotent: true, fn: (args: { input: ApiBudgetReserve; now: number }) => reserveApiBudget(args.input, args.now) },
|
|
14
|
-
"apiBudget.report": { idempotent: true, fn: (args: { input: ApiBudgetReport; now: number }) => reportApiBudget(args.input, args.now) },
|
|
10
|
+
"apiBudget.reconcile": { offLoop: true, idempotent: true, fn: (args: { now: number }) => reconcileApiBudgetSessions(args.now) },
|
|
11
|
+
"apiBudget.pace": { offLoop: true, idempotent: true, fn: (args: { leadTimeMs: number; now: number; budgetId?: ApiBudgetId }) => apiBudgetPace(args.leadTimeMs, args.now, args.budgetId) },
|
|
12
|
+
"apiBudget.configure": { offLoop: true, idempotent: true, fn: (args: { input: ApiBudgetConfig; now: number }) => configureApiBudget(args.input, args.now) },
|
|
13
|
+
"apiBudget.reserve": { offLoop: true, idempotent: true, fn: (args: { input: ApiBudgetReserve; now: number }) => reserveApiBudget(args.input, args.now) },
|
|
14
|
+
"apiBudget.report": { offLoop: true, idempotent: true, fn: (args: { input: ApiBudgetReport; now: number }) => reportApiBudget(args.input, args.now) },
|
|
15
15
|
};
|
|
@@ -44,13 +44,13 @@ function put(args: Put): boolean {
|
|
|
44
44
|
|
|
45
45
|
export const CLAUDE_APP_OAUTH_READ_OPS = { "claudeAppOAuth.get": get };
|
|
46
46
|
export const CLAUDE_APP_OAUTH_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = {
|
|
47
|
-
"claudeAppOAuth.put": { idempotent: false, fn: put },
|
|
48
|
-
"claudeAppOAuth.take": { idempotent: false, fn: (args: Key) => {
|
|
47
|
+
"claudeAppOAuth.put": { offLoop: true, idempotent: false, fn: put },
|
|
48
|
+
"claudeAppOAuth.take": { offLoop: true, idempotent: false, fn: (args: Key) => {
|
|
49
49
|
const value = get(args);
|
|
50
50
|
getDb().query("DELETE FROM claude_app_oauth WHERE resource=? AND kind=? AND key=?").run(args.resource, args.kind, args.key);
|
|
51
51
|
return value;
|
|
52
52
|
} },
|
|
53
|
-
"claudeAppOAuth.issue": { idempotent: false, fn: (args: Put & { refreshKey?: string; clientId: string }) => {
|
|
53
|
+
"claudeAppOAuth.issue": { offLoop: true, idempotent: false, fn: (args: Put & { refreshKey?: string; clientId: string }) => {
|
|
54
54
|
// Consume and replace in the same writer transaction; concurrent refreshes cannot resurrect a revoked family.
|
|
55
55
|
let old: { clientId: string; family: string; type: string; used?: boolean } | undefined;
|
|
56
56
|
if (args.refreshKey) {
|
|
@@ -65,7 +65,7 @@ export const CLAUDE_APP_OAUTH_WRITE_OPS: Readonly<Record<string, WriterOpSpec>>
|
|
|
65
65
|
.run(JSON.stringify({ ...old, used: true }), args.resource, args.kind, args.refreshKey!);
|
|
66
66
|
return "ok";
|
|
67
67
|
} },
|
|
68
|
-
"claudeAppOAuth.revoke": { idempotent: true, fn: (args: Key & { clientId: string }) => {
|
|
68
|
+
"claudeAppOAuth.revoke": { offLoop: true, idempotent: true, fn: (args: Key & { clientId: string }) => {
|
|
69
69
|
const value = get(args);
|
|
70
70
|
const credential = value ? JSON.parse(value) as { clientId: string; family: string } : null;
|
|
71
71
|
if (credential?.clientId === args.clientId) removeFamily(args.resource, credential.family);
|
|
@@ -3,16 +3,19 @@ import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
|
|
|
3
3
|
|
|
4
4
|
export const CODEX_RESET_CONSUMES_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
5
5
|
"codexResetConsumes.begin": {
|
|
6
|
+
offLoop: true,
|
|
6
7
|
idempotent: true,
|
|
7
8
|
fn: (args: { input: Parameters<typeof beginCodexResetConsume>[0] }, ctx: WriterOpContext) =>
|
|
8
9
|
beginCodexResetConsume({ ...args.input, now: args.input.now ?? ctx.now }),
|
|
9
10
|
},
|
|
10
11
|
"codexResetConsumes.complete": {
|
|
12
|
+
offLoop: true,
|
|
11
13
|
idempotent: true,
|
|
12
14
|
fn: (args: { input: Parameters<typeof completeCodexResetConsume>[0] }, ctx: WriterOpContext) =>
|
|
13
15
|
completeCodexResetConsume({ ...args.input, now: args.input.now ?? ctx.now }),
|
|
14
16
|
},
|
|
15
17
|
"codexResetConsumes.prune": {
|
|
18
|
+
offLoop: true,
|
|
16
19
|
idempotent: true,
|
|
17
20
|
fn: (args: { olderThan?: number }, ctx: WriterOpContext) => pruneCodexResetConsumes(args.olderThan ?? ctx.now),
|
|
18
21
|
},
|
|
@@ -100,11 +100,13 @@ function markLandingReplay(result: unknown): unknown {
|
|
|
100
100
|
|
|
101
101
|
export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
102
102
|
"commands.claimPending": {
|
|
103
|
+
offLoop: true,
|
|
103
104
|
idempotent: true,
|
|
104
105
|
fn: (args: { id: string; expectedType?: string; ownerToken?: string }, ctx) =>
|
|
105
106
|
claimPendingCommand(args.id, args.expectedType, args.ownerToken, ctx.now),
|
|
106
107
|
},
|
|
107
108
|
"commands.createAgentCleanupIfAbsent": {
|
|
109
|
+
offLoop: true,
|
|
108
110
|
idempotent: false,
|
|
109
111
|
fn: (args: { target: string; id: string }, ctx: WriterOpContext) => hasActiveAgentCleanup(args.target) ? null
|
|
110
112
|
: createCommandInternal({ id: args.id, input: { type: "agent.cleanup", source: "system", target: args.target,
|
|
@@ -117,10 +119,12 @@ export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
117
119
|
fn: (args: { command: Command }, ctx: WriterOpContext) => settleAgentCleanupWorkspaces(args.command, ctx.now),
|
|
118
120
|
},
|
|
119
121
|
"commands.reconcileScheduler": {
|
|
122
|
+
offLoop: true,
|
|
120
123
|
idempotent: true,
|
|
121
124
|
fn: (args: Parameters<typeof reconcileSchedulerCommandsBody>[0], ctx: WriterOpContext) => reconcileSchedulerCommandsBody(args, ctx.now),
|
|
122
125
|
},
|
|
123
126
|
"commands.createLegacyLanding": {
|
|
127
|
+
offLoop: true,
|
|
124
128
|
idempotent: false,
|
|
125
129
|
fn: (args: CreateLegacyLandingArgs, ctx: WriterOpContext) => createLegacyLandingBody(args, ctx.now),
|
|
126
130
|
serializeIdempotencyResult: redactCreateResult,
|
|
@@ -136,61 +140,74 @@ export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
136
140
|
recordIdempotencyResult: (result) => (result as { claimed?: boolean } | null)?.claimed === true,
|
|
137
141
|
},
|
|
138
142
|
"commands.acceptLandingProgress": {
|
|
143
|
+
offLoop: true,
|
|
139
144
|
idempotent: false,
|
|
140
145
|
fn: (args: AcceptLandingProgressArgs, ctx: WriterOpContext) => acceptLandingProgressBody(args, ctx.now),
|
|
141
146
|
},
|
|
142
147
|
"commands.acceptLandingResult": {
|
|
148
|
+
offLoop: true,
|
|
143
149
|
idempotent: false,
|
|
144
150
|
fn: (args: AcceptLandingResultArgs, ctx: WriterOpContext) => acceptLandingResultBody(args, ctx.now),
|
|
145
151
|
replayIdempotencyResult: markLandingReplay,
|
|
146
152
|
},
|
|
147
153
|
"commands.resolveLegacyPublication": {
|
|
154
|
+
offLoop: true,
|
|
148
155
|
idempotent: true,
|
|
149
156
|
fn: (args: ResolveLegacyPublicationArgs, ctx: WriterOpContext) => resolveLegacyPublicationBody(args, ctx.now),
|
|
150
157
|
},
|
|
151
158
|
"commands.acquireLegacyPublishFence": {
|
|
159
|
+
offLoop: true,
|
|
152
160
|
idempotent: false,
|
|
153
161
|
fn: (args: AcquireLegacyPublishFenceArgs, ctx: WriterOpContext) => acquireLegacyPublishFenceBody(args, ctx.now),
|
|
154
162
|
},
|
|
155
163
|
"commands.acquireKernelPublishFence": {
|
|
164
|
+
offLoop: true,
|
|
156
165
|
idempotent: false,
|
|
157
166
|
fn: (args: AcquireKernelPublishFenceArgs, ctx: WriterOpContext) => acquireKernelPublishFenceBody(args, ctx.now),
|
|
158
167
|
},
|
|
159
168
|
"commands.requestLandingRetirement": {
|
|
169
|
+
offLoop: true,
|
|
160
170
|
idempotent: false,
|
|
161
171
|
fn: (args: RequestLandingRetirementArgs, ctx: WriterOpContext) =>
|
|
162
172
|
requestLandingRetirementBody(args, ctx.now),
|
|
163
173
|
},
|
|
164
174
|
"commands.grantKernelDryRunOwnership": {
|
|
175
|
+
offLoop: true,
|
|
165
176
|
idempotent: false,
|
|
166
177
|
fn: (args: GrantKernelDryRunOwnershipArgs, ctx: WriterOpContext) =>
|
|
167
178
|
grantKernelDryRunOwnershipBody(args, ctx.now),
|
|
168
179
|
},
|
|
169
180
|
"commands.consumeLegacyPublishPermit": {
|
|
181
|
+
offLoop: true,
|
|
170
182
|
idempotent: false,
|
|
171
183
|
fn: (args: ConsumeLegacyPublishPermitArgs, ctx: WriterOpContext) => consumeLegacyPublishPermitBody(args, ctx.now),
|
|
172
184
|
},
|
|
173
185
|
"commands.recordExecutionProof": {
|
|
186
|
+
offLoop: true,
|
|
174
187
|
idempotent: false,
|
|
175
188
|
fn: (args: RecordExecutionProofArgs, ctx: WriterOpContext) => recordExecutionProofBody(args, ctx.now),
|
|
176
189
|
replayIdempotencyResult: markLandingReplay,
|
|
177
190
|
},
|
|
178
191
|
"commands.terminalizeLegacyLanding": {
|
|
192
|
+
offLoop: true,
|
|
179
193
|
idempotent: false,
|
|
180
194
|
fn: (args: TerminalizeLegacyLandingArgs, ctx: WriterOpContext) => terminalizeLegacyLandingBody(args, ctx.now),
|
|
181
195
|
replayIdempotencyResult: markLandingReplay,
|
|
182
196
|
},
|
|
183
197
|
"commands.cancelPendingLegacyLandings": {
|
|
198
|
+
offLoop: true,
|
|
184
199
|
idempotent: true,
|
|
185
200
|
fn: (args: CancelPendingLegacyLandingsArgs, ctx: WriterOpContext) => cancelPendingLegacyLandingsBody(args, ctx.now),
|
|
186
201
|
},
|
|
187
202
|
// #2609 — the withdraw's legacy-lane mid-flight cancel (pending delegates to the never-started
|
|
188
203
|
// path; accepted/running is a fenced status CAS + publish-permit retirement).
|
|
189
204
|
"commands.cancelLegacyLandingExecution": {
|
|
205
|
+
offLoop: true,
|
|
190
206
|
idempotent: false,
|
|
191
207
|
fn: (args: CancelLegacyLandingExecutionArgs, ctx: WriterOpContext) => cancelLegacyLandingExecutionBody(args, ctx.now),
|
|
192
208
|
},
|
|
193
209
|
"commands.backfillLegacyLandingPage": {
|
|
210
|
+
offLoop: true,
|
|
194
211
|
idempotent: true,
|
|
195
212
|
fn: (args: { rows: BackfillLegacyLandingRow[] }, ctx: WriterOpContext) => backfillLegacyLandingPageBody(args, ctx.now),
|
|
196
213
|
},
|
|
@@ -221,6 +238,7 @@ export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
221
238
|
// only when `commandUpdateHasPostCommitEffects` says there is something to do. Idempotent: the
|
|
222
239
|
// terminal-event record is guarded by an existence check and the pill re-sets the same meta keys.
|
|
223
240
|
"commands.applyUpdatePostCommit": {
|
|
241
|
+
offLoop: true,
|
|
224
242
|
idempotent: true,
|
|
225
243
|
fn: (args: ApplyCommandUpdatePostCommitOpArgs): null => {
|
|
226
244
|
applyCommandUpdatePostCommit(args.previousStatus, args.command);
|
|
@@ -230,6 +248,7 @@ export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
230
248
|
// Spawn reaping must not overwrite a concurrent async success settlement. The status predicate
|
|
231
249
|
// lives inside this writer transaction, making the rows-changed result a real CAS winner signal.
|
|
232
250
|
"commands.cancelIfStatus": {
|
|
251
|
+
offLoop: true,
|
|
233
252
|
idempotent: false,
|
|
234
253
|
fn: (args: CancelCommandIfStatusWriteArgs, ctx: WriterOpContext) => cancelCommandIfStatusInternal(args, ctx.now),
|
|
235
254
|
serializeIdempotencyResult: redactCancelResult,
|
|
@@ -243,6 +262,7 @@ export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
243
262
|
// submission recovers the SAME {command, created} from the ledger (so the emit survives an
|
|
244
263
|
// indeterminate ack), while a genuine later re-drive presents a fresh id and finds `created:false`.
|
|
245
264
|
"commands.createKernelIfAbsent": {
|
|
265
|
+
offLoop: true,
|
|
246
266
|
idempotent: false,
|
|
247
267
|
fn: (args: CreateKernelCommandIfAbsentArgs, ctx: WriterOpContext) => {
|
|
248
268
|
let input = args.input;
|
|
@@ -278,6 +298,7 @@ export const COMMANDS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
278
298
|
// One caller's quota admission is all-or-nothing: the live + in-flight reads and INSERT share this
|
|
279
299
|
// operation's transaction, rather than independent-caller coalescing/savepoints.
|
|
280
300
|
"commands.createSpawnWithinQuota": {
|
|
301
|
+
offLoop: true,
|
|
281
302
|
idempotent: false,
|
|
282
303
|
fn: (args: CreateSpawnCommandWithinQuotaWriteArgs, ctx: WriterOpContext) =>
|
|
283
304
|
createSpawnCommandWithinQuotaInternal(args, ctx.now),
|
|
@@ -43,6 +43,7 @@ export const CONFIG_STORE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
|
|
|
43
43
|
// retry would double-bump/duplicate. The gateway requires an idempotencyKey (the facade supplies a
|
|
44
44
|
// per-submission one) and dedups on it in the SAME transaction as the write.
|
|
45
45
|
"config.set": {
|
|
46
|
+
offLoop: true,
|
|
46
47
|
idempotent: false,
|
|
47
48
|
idempotencySnapshotKey: (args: SetConfigOpArgs) => JSON.stringify([args.namespace, args.key]),
|
|
48
49
|
fn: (args: SetConfigOpArgs, ctx: WriterOpContext): ConfigEntry =>
|
|
@@ -51,6 +52,7 @@ export const CONFIG_STORE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
|
|
|
51
52
|
// Idempotent by construction: deleting again finds no row (`existing` null → early false) and writes
|
|
52
53
|
// nothing, so re-applying converges. No key required.
|
|
53
54
|
"config.delete": {
|
|
55
|
+
offLoop: true,
|
|
54
56
|
idempotent: true,
|
|
55
57
|
fn: (args: DeleteConfigOpArgs, ctx: WriterOpContext): boolean =>
|
|
56
58
|
deleteConfigInternal(args.namespace, args.key, args.updatedBy, ctx.now),
|
|
@@ -3,11 +3,13 @@ import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
|
|
|
3
3
|
|
|
4
4
|
export const CONTINUATION_ARCHIVES_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
5
5
|
"continuationArchives.archive": {
|
|
6
|
+
offLoop: true,
|
|
6
7
|
idempotent: false,
|
|
7
8
|
fn: (args: { input: Parameters<typeof archiveContinuationSegment>[0] }, ctx: WriterOpContext) =>
|
|
8
9
|
archiveContinuationSegment({ ...args.input, now: args.input.now ?? ctx.now }),
|
|
9
10
|
},
|
|
10
11
|
"continuationArchives.compact": {
|
|
12
|
+
offLoop: true,
|
|
11
13
|
idempotent: false,
|
|
12
14
|
fn: (args: { input: Parameters<typeof archiveAndAdvanceContinuation>[0] }, ctx: WriterOpContext) =>
|
|
13
15
|
archiveAndAdvanceContinuation({ ...args.input, now: args.input.now ?? ctx.now }),
|
|
@@ -40,23 +40,27 @@ export interface ApplyDeliveryActionOpArgs {
|
|
|
40
40
|
|
|
41
41
|
export const DELIVERY_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze({
|
|
42
42
|
"delivery.recordChannelFanoutSuppression": {
|
|
43
|
+
offLoop: true,
|
|
43
44
|
idempotent: true,
|
|
44
45
|
fn: (args: { messageId: number; channelAgentId: string; reason: string }, ctx: WriterOpContext) =>
|
|
45
46
|
recordChannelFanoutSuppression(args.messageId, args.channelAgentId, args.reason, ctx.now),
|
|
46
47
|
},
|
|
47
48
|
"delivery.recordAttempt": {
|
|
49
|
+
offLoop: true,
|
|
48
50
|
idempotent: false,
|
|
49
51
|
idempotencySnapshotKey: (args: RecordDeliveryAttemptOpArgs) => String(args.messageId),
|
|
50
52
|
fn: (args: RecordDeliveryAttemptOpArgs, ctx: WriterOpContext) =>
|
|
51
53
|
recordMessageDeliveryAttempt(args.messageId, args.input, ctx.now),
|
|
52
54
|
},
|
|
53
55
|
"delivery.applyAction": {
|
|
56
|
+
offLoop: true,
|
|
54
57
|
idempotent: false,
|
|
55
58
|
idempotencySnapshotKey: (args: ApplyDeliveryActionOpArgs) => String(args.messageId),
|
|
56
59
|
fn: (args: ApplyDeliveryActionOpArgs, ctx: WriterOpContext) =>
|
|
57
60
|
applyMessageDeliveryAction(args.messageId, args.input, ctx.now),
|
|
58
61
|
},
|
|
59
62
|
"delivery.reevaluate": {
|
|
63
|
+
offLoop: true,
|
|
60
64
|
idempotent: true,
|
|
61
65
|
fn: (args: ApplyDeliveryActionOpArgs, ctx: WriterOpContext) =>
|
|
62
66
|
applyMessageDeliveryAction(args.messageId, args.input, ctx.now),
|
|
@@ -66,6 +70,7 @@ export const DELIVERY_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Object
|
|
|
66
70
|
// elapsed time (chiefly tests) needs to drive it directly rather than only via real wall-clock
|
|
67
71
|
// submission time. Production callers omit it and get ctx.now, per H6.
|
|
68
72
|
"delivery.expireQueued": {
|
|
73
|
+
offLoop: true,
|
|
69
74
|
idempotent: true,
|
|
70
75
|
fn: (args: { now?: number }, ctx: WriterOpContext) => expireQueuedMessagesWithNotices(args?.now ?? ctx.now),
|
|
71
76
|
},
|