@cat-factory/worker 0.176.0 → 0.177.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareContainerTransport.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/containers/CloudflareContainerTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAqE5E,qBAAa,4BAA6B,YAAW,eAAe;IAUhE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAG1B,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IArBhC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,0BAAyB;IAEzC,YAMmB,SAAS,EACtB,sBAAsB,CAAC,kBAAkB,CAAC,GAC1C,sBAAsB,CAAC,eAAe,CAAC;IAC3C,kFAAkF;IACjE,QAAQ,CAAC,EAAE,yBAAyB,YAAA;IACrD;;;;;;OAMG;IACc,YAAY,CAAC,EAAE,MAAM,YAAA,EACpC;IAEJ,oFAAoF;IACpF,OAAO,CAAC,YAAY;IAUd,QAAQ,CACZ,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,kBAA4B,GACjC,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CA+BxC;IAED;;;;;;;;;;;;OAYG;YACW,UAAU;IASlB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAyCpD;IAED;;;;;OAKG;IACG,OAAO,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9C;IAED;;;;;;;;;;OAUG;IACG,OAAO,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqB9D;CACF"}
1
+ {"version":3,"file":"CloudflareContainerTransport.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/containers/CloudflareContainerTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAO9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAqF5E,qBAAa,4BAA6B,YAAW,eAAe;IAUhE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAG1B,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IArBhC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,0BAAyB;IAEzC,YAMmB,SAAS,EACtB,sBAAsB,CAAC,kBAAkB,CAAC,GAC1C,sBAAsB,CAAC,eAAe,CAAC;IAC3C,kFAAkF;IACjE,QAAQ,CAAC,EAAE,yBAAyB,YAAA;IACrD;;;;;;OAMG;IACc,YAAY,CAAC,EAAE,MAAM,YAAA,EACpC;IAEJ,oFAAoF;IACpF,OAAO,CAAC,YAAY;IAUd,QAAQ,CACZ,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,kBAA4B,GACjC,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CA+BxC;IAED;;;;;;;;;;;;OAYG;YACW,UAAU;IAWlB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAyCpD;IAED;;;;;OAKG;IACG,OAAO,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9C;IAED;;;;;;;;;;OAUG;IACG,OAAO,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqB9D;CACF"}
@@ -1,5 +1,5 @@
1
- import { CONTAINER_EVICTION_ERROR, harnessDispatchError, readRunnerDispatchAck, } from '@cat-factory/kernel';
2
- import { isRolloutSignal } from './stopCause';
1
+ import { composePostMortem, CONTAINER_EVICTION_ERROR, harnessDispatchError, readRunnerDispatchAck, } from '@cat-factory/kernel';
2
+ import { describeContainerExit, isRolloutSignal, } from './stopCause';
3
3
  // The human-readable message for a failed poll the transport maps to a container eviction. The
4
4
  // eviction verdict the engine acts on rides the STRUCTURED `RunnerJobView.evicted` field
5
5
  // (`crash` / `transient`) minted alongside it. The "(container evicted or crashed)" wording and
@@ -24,14 +24,29 @@ const TRANSIENT_EVICTION_ERROR = {
24
24
  idle: IDLE_EVICTION_ERROR,
25
25
  };
26
26
  /**
27
- * The failed view for a 404 poll, given what the container itself observed about its reclaim.
28
- * No observed cause ⇒ the container is simply gone with nothing to explain it, which is a
29
- * `crash` (an OOM, a genuine fault) and recovers on the small budget.
27
+ * The failed view for a 404 poll, given what the container itself observed about its stop.
28
+ *
29
+ * The two halves of that observation answer different questions and are read independently. The
30
+ * `cause` decides the VERDICT: no observed cause ⇒ the container is simply gone with nothing to
31
+ * explain it, which is a `crash` (an OOM, a genuine fault) and recovers on the small budget. The
32
+ * `exit` state decides the DETAIL, and is attached whether or not a cause was recognised: on a
33
+ * runtime that cannot hand a log tail back to the Worker it is the only post-mortem there is,
34
+ * and a crash is exactly the case with no cause to name (finding D1).
35
+ *
36
+ * Independent does not mean unaware of each other. The cause is handed to the detail's wording
37
+ * so the two cannot contradict: the same reclaim that mints the `transient` verdict here is
38
+ * performed with a SIGKILL, and an exit code read as if nothing else explained it would tell the
39
+ * operator "out-of-memory kill" directly underneath "idle container reclaimed between polls".
30
40
  */
31
- function evictionView(cause) {
32
- if (!cause)
33
- return { state: 'failed', error: EVICTION_ERROR, evicted: 'crash' };
34
- return { state: 'failed', error: TRANSIENT_EVICTION_ERROR[cause], evicted: 'transient' };
41
+ function evictionView(observed) {
42
+ const detail = composePostMortem([describeContainerExit(observed.exit, observed.cause)]);
43
+ const { cause } = observed;
44
+ return {
45
+ state: 'failed',
46
+ error: cause ? TRANSIENT_EVICTION_ERROR[cause] : EVICTION_ERROR,
47
+ evicted: cause ? 'transient' : 'crash',
48
+ ...(detail ? { detail } : {}),
49
+ };
35
50
  }
36
51
  // The default runner transport: a per-RUN Cloudflare Container. One Durable Object
37
52
  // instance per run id (`ref.runId`) hosts that run's whole sequence of step jobs; the
@@ -71,7 +86,7 @@ export class CloudflareContainerTransport {
71
86
  constructor(
72
87
  // Either per-run container class: `ExecutionContainer` (the agent harness, bound as
73
88
  // `EXEC_CONTAINER`) or `DeployContainer` (the deploy harness, bound as `DEPLOY_CONTAINER`).
74
- // Both expose the same `/jobs` HTTP contract on 8080 plus `recentEvictionCause`/`shutdown`,
89
+ // Both expose the same `/jobs` HTTP contract on 8080 plus `recentStopObservation`/`shutdown`,
75
90
  // so this transport drives either unchanged — a deploy-dedicated instance simply gets the
76
91
  // deploy namespace.
77
92
  namespace,
@@ -145,8 +160,10 @@ export class CloudflareContainerTransport {
145
160
  * transport OBSERVED itself wins over a read that could not happen.
146
161
  */
147
162
  async evictionOf(stub, ref, observed) {
148
- const claimed = await stub.recentEvictionCause(ref.jobId).catch(() => undefined);
149
- return evictionView(observed ?? claimed);
163
+ const claimed = await stub.recentStopObservation(ref.jobId).catch(() => ({}));
164
+ // The transport's own observation wins for the CAUSE (see above); the exit state can only
165
+ // ever come from the container, so it rides through whichever way the cause was decided.
166
+ return evictionView({ ...claimed, ...(observed ? { cause: observed } : {}) });
150
167
  }
151
168
  async poll(ref) {
152
169
  // The per-run container is the Durable Object addressed by the run id; its DO id is
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareContainerTransport.js","sourceRoot":"","sources":["../../../src/infrastructure/containers/CloudflareContainerTransport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CloudflareContainerTransport.js","sourceRoot":"","sources":["../../../src/infrastructure/containers/CloudflareContainerTransport.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { Container } from '@cloudflare/containers';
2
2
  import type { StopParams } from '@cloudflare/containers';
3
3
  import type { Env } from '../env';
4
- import { type ContainerStopCause } from './stopCause';
4
+ import { type StopObservation } from './stopCause';
5
5
  /**
6
6
  * The behaviour every per-run Cloudflare Container shares: one Durable Object instance per run
7
7
  * id hosts that run's sequence of jobs, the harness listens on 8080, and the base
@@ -23,21 +23,62 @@ export declare abstract class RunContainer extends Container<Env> {
23
23
  defaultPort: number;
24
24
  envVars: Record<string, string>;
25
25
  sleepAfter: string;
26
+ /**
27
+ * Whether the stop this container is about to observe is one WE asked for: the idle reclaim in
28
+ * {@link onActivityExpired} and the deliberate teardown in {@link shutdown}, both of which stop
29
+ * the container by signalling it.
30
+ *
31
+ * It exists because the resulting exit state is evidence about nothing. We sent the signal, so
32
+ * the code that comes back describes our own request (and escalates to a SIGKILL 137 whenever
33
+ * the harness does not exit inside the platform's grace period), while the account the run
34
+ * actually needs is the cause already recorded beside it. Left recorded, that exit is read back
35
+ * as the container's cause of death and reported as "most often an out-of-memory kill" under a
36
+ * verdict that says the platform reclaimed an idle container.
37
+ *
38
+ * In-memory rather than persisted, and reset by {@link onStart}: it describes ONE stop of ONE
39
+ * container life, and the isolate cannot outlive the stop it is set for. A container that comes
40
+ * back up has a real death ahead of it again.
41
+ */
42
+ private selfInitiatedStop;
43
+ /**
44
+ * A container life is starting, so nothing from here on is a stop we asked for.
45
+ *
46
+ * The base class flushes any deferred `onStop` from the PREVIOUS life before it calls this, so
47
+ * the reset can never land between a self-initiated stop and the hook that observes it.
48
+ */
49
+ onStart(): Promise<void>;
26
50
  /**
27
51
  * Record that THIS run's container was drained by a new-version rollout (a deploy, exit 143)
28
52
  * rather than crashing. The transport's next job poll (which 404s once the container restarts
29
- * empty) reads this back through {@link recentEvictionCause}, so the engine
53
+ * empty) reads this back through {@link recentStopObservation}, so the engine
30
54
  * recovers it on the larger transient budget instead of failing the run as a crash.
31
55
  * Persisted to DO storage (not in-memory) so it survives the isolate reset a combined
32
56
  * worker+container deploy causes.
33
57
  */
34
58
  onError(error: unknown): Promise<unknown>;
35
59
  /**
36
- * Belt-and-braces: depending on the runtime version, a rollout drain can surface as a
37
- * `runtime_signal` stop with SIGTERM (143) through `onStop` instead of (or as well as)
38
- * `onError`. Record it the same way.
60
+ * Record EVERY stop, with whatever this hook can say about it.
61
+ *
62
+ * Two things ride the same write. A `runtime_signal` SIGTERM (143) is a rollout drain, which
63
+ * depending on the runtime version surfaces here instead of (or as well as) through `onError`,
64
+ * so it is recorded as that cause the same way. And the `{ exitCode, reason }` pair itself is
65
+ * recorded for every stop, cause or not, because it is the ONLY account of a death this
66
+ * runtime can keep: a Cloudflare Container's stdout goes to the deployment's Workers logs and
67
+ * nothing here can read it back, so without this an OOM-killed agent reaches the operator as
68
+ * "container evicted or crashed" and nothing else (finding D1).
69
+ *
70
+ * Recording an exit changes no verdict: the transient/crash classification still comes from
71
+ * the cause alone, so a plain crash keeps spending the crash budget and merely says why.
72
+ *
73
+ * A stop WE asked for is the exception, and records nothing. Its exit state is the echo of our
74
+ * own signal rather than an observation, and the cause that explains it has already been
75
+ * recorded by whoever asked (see {@link selfInitiatedStop}).
76
+ *
77
+ * Both rules live in {@link observationForStop}, beside the other half of "what does this stop
78
+ * mean" (`isRolloutSignal`) and where a plain unit test can reach them: nothing in this class
79
+ * can be exercised without a real Durable Object.
39
80
  */
40
- onStop(params: StopParams): void;
81
+ onStop(params: StopParams): Promise<void>;
41
82
  /**
42
83
  * The idle window elapsed. Record it as the reclaim cause it is, then reclaim as the base
43
84
  * class would.
@@ -75,20 +116,31 @@ export declare abstract class RunContainer extends Container<Env> {
75
116
  */
76
117
  fetch(request: Request): Promise<Response>;
77
118
  /**
78
- * Why this run's container went away, for the transport to read over RPC after `jobId`'s poll
79
- * 404s: the one thing that tells a reclaim apart from a crash. `undefined` means no reclaim
80
- * this container observed explains that 404, so the caller reports a crash.
119
+ * What this run's container observed about its own stop, for the transport to read over RPC
120
+ * after `jobId`'s poll 404s: the `cause` that tells a reclaim apart from a crash, and the
121
+ * `exit` state that is the only surviving account of the death itself. An EMPTY observation
122
+ * means nothing this container saw explains that 404, so the caller reports a bare crash.
81
123
  *
82
124
  * Claimed by the polling job rather than deleted, so a retried durable poll step re-reads the
83
125
  * same answer while a different job still finds it spent. See {@link takeStopCause}.
84
126
  */
85
- recentEvictionCause(jobId: string): Promise<ContainerStopCause | undefined>;
127
+ recentStopObservation(jobId: string): Promise<StopObservation>;
86
128
  /**
87
129
  * Reclaim this container now (SIGKILL via the base class), rather than waiting for the
88
130
  * `sleepAfter` idle timer. Called over RPC when a run settles or faults, so a leaked instance
89
131
  * isn't billed while idle. Best-effort and idempotent: destroying an already-stopped
90
132
  * container is a no-op, and we swallow any error so the caller's failure handling is never
91
133
  * derailed by cleanup.
134
+ *
135
+ * It is also where the stop record's life ENDS. A record is written on every stop and deleted
136
+ * only when a NEW job is accepted, so without this the last one a run ever observed sits in
137
+ * that run's Durable Object for good: the run is over, no dispatch is coming to clear it, and
138
+ * the value is a diagnostic nobody will read again. Deleting it here is what keeps the whole
139
+ * mechanism transient rather than a per-run key that accumulates for the lifetime of the
140
+ * deployment.
141
+ *
142
+ * Ordered after the teardown so the destroy's own `onStop` cannot land behind the delete,
143
+ * belt-and-braces with {@link selfInitiatedStop}, which stops that hook writing at all.
92
144
  */
93
145
  shutdown(): Promise<void>;
94
146
  private record;
@@ -1 +1 @@
1
- {"version":3,"file":"RunContainer.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/containers/RunContainer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAEL,KAAK,kBAAkB,EAKxB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;;;;;;;GAeG;AACH,8BAAsB,YAAa,SAAQ,SAAS,CAAC,GAAG,CAAC;IACvD,wFAAwF;IAC/E,WAAW,SAAO;IAKlB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAElC;IAMG,UAAU,SAAQ;IAE3B;;;;;;;OAOG;IACY,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAIvD;IAED;;;;OAIG;IACM,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAIxC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACY,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAGhD;IAED;;;;;;;;;;;;;;OAcG;IACY,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAWxD;IAED;;;;;;;OAOG;IACG,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAEhF;IAED;;;;;;OAMG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAO9B;IAED,OAAO,CAAC,MAAM;IAId,wEAAwE;IACxE,OAAO,KAAK,OAAO,GAElB;CACF"}
1
+ {"version":3,"file":"RunContainer.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/containers/RunContainer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;;;;;;;GAeG;AACH,8BAAsB,YAAa,SAAQ,SAAS,CAAC,GAAG,CAAC;IACvD,wFAAwF;IAC/E,WAAW,SAAO;IAKlB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAElC;IAMG,UAAU,SAAQ;IAE3B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,iBAAiB,CAAQ;IAEjC;;;;;OAKG;IACY,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAGtC;IAED;;;;;;;OAOG;IACY,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAIvD;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACY,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvD;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACY,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAShD;IAED;;;;;;;;;;;;;;OAcG;IACY,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAWxD;IAED;;;;;;;;OAQG;IACG,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAEnE;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAc9B;IAED,OAAO,CAAC,MAAM;IAId,wEAAwE;IACxE,OAAO,KAAK,OAAO,GAElB;CACF"}
@@ -1,7 +1,7 @@
1
1
  import { Container } from '@cloudflare/containers';
2
2
  import { runBestEffort } from '@cat-factory/kernel';
3
3
  import { logger } from '../observability/logger';
4
- import { clearStopCause, isRolloutSignal, recordStopCause, takeStopCause, } from './stopCause';
4
+ import { clearStopCause, isRolloutSignal, observationForStop, recordStopCause, takeStopCause, } from './stopCause';
5
5
  /**
6
6
  * The behaviour every per-run Cloudflare Container shares: one Durable Object instance per run
7
7
  * id hosts that run's sequence of jobs, the harness listens on 8080, and the base
@@ -32,29 +32,73 @@ export class RunContainer extends Container {
32
32
  // keeps it warm, which is why an elapsed window is recorded as a reclaim cause rather than
33
33
  // left to read as a crash. See `onActivityExpired`.
34
34
  sleepAfter = '10m';
35
+ /**
36
+ * Whether the stop this container is about to observe is one WE asked for: the idle reclaim in
37
+ * {@link onActivityExpired} and the deliberate teardown in {@link shutdown}, both of which stop
38
+ * the container by signalling it.
39
+ *
40
+ * It exists because the resulting exit state is evidence about nothing. We sent the signal, so
41
+ * the code that comes back describes our own request (and escalates to a SIGKILL 137 whenever
42
+ * the harness does not exit inside the platform's grace period), while the account the run
43
+ * actually needs is the cause already recorded beside it. Left recorded, that exit is read back
44
+ * as the container's cause of death and reported as "most often an out-of-memory kill" under a
45
+ * verdict that says the platform reclaimed an idle container.
46
+ *
47
+ * In-memory rather than persisted, and reset by {@link onStart}: it describes ONE stop of ONE
48
+ * container life, and the isolate cannot outlive the stop it is set for. A container that comes
49
+ * back up has a real death ahead of it again.
50
+ */
51
+ selfInitiatedStop = false;
52
+ /**
53
+ * A container life is starting, so nothing from here on is a stop we asked for.
54
+ *
55
+ * The base class flushes any deferred `onStop` from the PREVIOUS life before it calls this, so
56
+ * the reset can never land between a self-initiated stop and the hook that observes it.
57
+ */
58
+ async onStart() {
59
+ this.selfInitiatedStop = false;
60
+ await super.onStart();
61
+ }
35
62
  /**
36
63
  * Record that THIS run's container was drained by a new-version rollout (a deploy, exit 143)
37
64
  * rather than crashing. The transport's next job poll (which 404s once the container restarts
38
- * empty) reads this back through {@link recentEvictionCause}, so the engine
65
+ * empty) reads this back through {@link recentStopObservation}, so the engine
39
66
  * recovers it on the larger transient budget instead of failing the run as a crash.
40
67
  * Persisted to DO storage (not in-memory) so it survives the isolate reset a combined
41
68
  * worker+container deploy causes.
42
69
  */
43
70
  async onError(error) {
44
71
  if (isRolloutSignal(error))
45
- await this.record('rollout');
72
+ await this.record({ cause: 'rollout' });
46
73
  // Preserve the base behaviour (log + rethrow) so nothing else changes.
47
74
  return super.onError(error);
48
75
  }
49
76
  /**
50
- * Belt-and-braces: depending on the runtime version, a rollout drain can surface as a
51
- * `runtime_signal` stop with SIGTERM (143) through `onStop` instead of (or as well as)
52
- * `onError`. Record it the same way.
77
+ * Record EVERY stop, with whatever this hook can say about it.
78
+ *
79
+ * Two things ride the same write. A `runtime_signal` SIGTERM (143) is a rollout drain, which
80
+ * depending on the runtime version surfaces here instead of (or as well as) through `onError`,
81
+ * so it is recorded as that cause the same way. And the `{ exitCode, reason }` pair itself is
82
+ * recorded for every stop, cause or not, because it is the ONLY account of a death this
83
+ * runtime can keep: a Cloudflare Container's stdout goes to the deployment's Workers logs and
84
+ * nothing here can read it back, so without this an OOM-killed agent reaches the operator as
85
+ * "container evicted or crashed" and nothing else (finding D1).
86
+ *
87
+ * Recording an exit changes no verdict: the transient/crash classification still comes from
88
+ * the cause alone, so a plain crash keeps spending the crash budget and merely says why.
89
+ *
90
+ * A stop WE asked for is the exception, and records nothing. Its exit state is the echo of our
91
+ * own signal rather than an observation, and the cause that explains it has already been
92
+ * recorded by whoever asked (see {@link selfInitiatedStop}).
93
+ *
94
+ * Both rules live in {@link observationForStop}, beside the other half of "what does this stop
95
+ * mean" (`isRolloutSignal`) and where a plain unit test can reach them: nothing in this class
96
+ * can be exercised without a real Durable Object.
53
97
  */
54
- onStop(params) {
55
- if (params.reason === 'runtime_signal' && params.exitCode === 143) {
56
- void this.record('rollout');
57
- }
98
+ async onStop(params) {
99
+ const observed = observationForStop(params, this.selfInitiatedStop);
100
+ if (observed)
101
+ await this.record(observed);
58
102
  }
59
103
  /**
60
104
  * The idle window elapsed. Record it as the reclaim cause it is, then reclaim as the base
@@ -76,8 +120,13 @@ export class RunContainer extends Container {
76
120
  * that case, so a marker would be attributing a reclaim that never happened.
77
121
  */
78
122
  async onActivityExpired() {
79
- if (this.ctx.container?.running)
80
- await this.record('idle');
123
+ if (this.ctx.container?.running) {
124
+ await this.record({ cause: 'idle' });
125
+ // The reclaim below is us signalling the container, so the exit it reports is our own
126
+ // request coming back (a SIGKILL 137 whenever the harness does not exit inside the grace
127
+ // period) and not a death to attribute. The cause just recorded is the whole account.
128
+ this.selfInitiatedStop = true;
129
+ }
81
130
  await super.onActivityExpired();
82
131
  }
83
132
  /**
@@ -106,14 +155,15 @@ export class RunContainer extends Container {
106
155
  return res;
107
156
  }
108
157
  /**
109
- * Why this run's container went away, for the transport to read over RPC after `jobId`'s poll
110
- * 404s: the one thing that tells a reclaim apart from a crash. `undefined` means no reclaim
111
- * this container observed explains that 404, so the caller reports a crash.
158
+ * What this run's container observed about its own stop, for the transport to read over RPC
159
+ * after `jobId`'s poll 404s: the `cause` that tells a reclaim apart from a crash, and the
160
+ * `exit` state that is the only surviving account of the death itself. An EMPTY observation
161
+ * means nothing this container saw explains that 404, so the caller reports a bare crash.
112
162
  *
113
163
  * Claimed by the polling job rather than deleted, so a retried durable poll step re-reads the
114
164
  * same answer while a different job still finds it spent. See {@link takeStopCause}.
115
165
  */
116
- async recentEvictionCause(jobId) {
166
+ async recentStopObservation(jobId) {
117
167
  return takeStopCause(this.storage, Date.now(), jobId);
118
168
  }
119
169
  /**
@@ -122,8 +172,19 @@ export class RunContainer extends Container {
122
172
  * isn't billed while idle. Best-effort and idempotent: destroying an already-stopped
123
173
  * container is a no-op, and we swallow any error so the caller's failure handling is never
124
174
  * derailed by cleanup.
175
+ *
176
+ * It is also where the stop record's life ENDS. A record is written on every stop and deleted
177
+ * only when a NEW job is accepted, so without this the last one a run ever observed sits in
178
+ * that run's Durable Object for good: the run is over, no dispatch is coming to clear it, and
179
+ * the value is a diagnostic nobody will read again. Deleting it here is what keeps the whole
180
+ * mechanism transient rather than a per-run key that accumulates for the lifetime of the
181
+ * deployment.
182
+ *
183
+ * Ordered after the teardown so the destroy's own `onStop` cannot land behind the delete,
184
+ * belt-and-braces with {@link selfInitiatedStop}, which stops that hook writing at all.
125
185
  */
126
186
  async shutdown() {
187
+ this.selfInitiatedStop = true;
127
188
  try {
128
189
  await this.destroy();
129
190
  }
@@ -131,9 +192,13 @@ export class RunContainer extends Container {
131
192
  // silent-catch-ok: already gone / not running, so there is nothing to reclaim and nothing
132
193
  // for an operator to do about it.
133
194
  }
195
+ // Best-effort: the container is reclaimed either way, and a retained key is a tidiness
196
+ // problem, never a correctness one. Reported rather than swallowed so a storage backend
197
+ // failing every delete does not stay invisible.
198
+ await runBestEffort(logger, 'clear container stop cause on shutdown', () => clearStopCause(this.storage));
134
199
  }
135
- record(cause) {
136
- return recordStopCause(this.storage, cause, Date.now());
200
+ record(observed) {
201
+ return recordStopCause(this.storage, observed, Date.now());
137
202
  }
138
203
  /** The DO storage, narrowed to what the stop-cause bookkeeping uses. */
139
204
  get storage() {
@@ -1 +1 @@
1
- {"version":3,"file":"RunContainer.js","sourceRoot":"","sources":["../../../src/infrastructure/containers/RunContainer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RunContainer.js","sourceRoot":"","sources":["../../../src/infrastructure/containers/RunContainer.ts"],"names":[],"mappings":"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"}
@@ -12,9 +12,30 @@
12
12
  export type ContainerStopCause = 'rollout' | 'idle';
13
13
  /** DO-storage key holding the {@link StopCauseRecord} of the most recent self-observed stop. */
14
14
  export declare const STOP_CAUSE_KEY = "containerStopCause";
15
- /** What the container persists when it observes its own reclaim. */
15
+ /**
16
+ * What the runtime reported when the workload stopped, mirroring the container base class's
17
+ * `StopParams`. Recorded for EVERY stop, including the ones no {@link ContainerStopCause}
18
+ * explains, because it answers a different question: the cause decides the recovery BUDGET, the
19
+ * exit state is the only account of the death anyone gets afterwards.
20
+ *
21
+ * A Cloudflare Container's stdout is delivered to the deployment's Workers logs and is not
22
+ * readable back from inside the Durable Object, so this is the whole of the post-mortem this
23
+ * runtime can mint. It is still the difference between "container evicted or crashed" and "exit
24
+ * code 137", i.e. between a run nobody can diagnose and an out-of-memory kill.
25
+ */
26
+ export interface ContainerExitState {
27
+ code: number;
28
+ reason: 'exit' | 'runtime_signal';
29
+ }
30
+ /** What the container persists when it observes its own stop. */
16
31
  export interface StopCauseRecord {
17
- cause: ContainerStopCause;
32
+ /**
33
+ * The infrastructure-churn cause, when the container recognised one. Absent for a stop it
34
+ * cannot explain (a crash, an OOM kill), which is exactly the case {@link exit} exists for.
35
+ */
36
+ cause?: ContainerStopCause;
37
+ /** What the runtime reported about the stop, when a hook that carries it fired. */
38
+ exit?: ContainerExitState;
18
39
  /** Epoch ms the stop was observed. */
19
40
  at: number;
20
41
  /**
@@ -49,6 +70,42 @@ export declare const ATTRIBUTION_WINDOW_MS: {
49
70
  rollout: number;
50
71
  idle: number;
51
72
  };
73
+ /**
74
+ * How long a stop this container could NOT attribute (a crash, an OOM kill) may still explain a
75
+ * 404 poll, i.e. the window governing a record that carries only an {@link ContainerExitState}.
76
+ *
77
+ * The same reasoning as the `idle` window, and deliberately the same number: what separates the
78
+ * death from the poll that discovers it is the POLL GAP, and a crash is discovered by the same
79
+ * ~15s tick an idle reclaim is, stretched by whatever delayed the driver. Sizing this to the
80
+ * rollout case instead would read every crash discovered after a re-drive as unexplained, which
81
+ * is the class this record exists to explain.
82
+ *
83
+ * Wide is affordable here for a reason the cause windows do not share: an exit state changes no
84
+ * verdict. It is attached to the failure `detail`, so the cost of over-attributing is a
85
+ * misleading sentence, never a wrongly-extended recovery budget.
86
+ */
87
+ export declare const EXIT_ATTRIBUTION_WINDOW_MS: number;
88
+ /**
89
+ * How far apart two hook calls may be and still be treated as observations of ONE stop, i.e. the
90
+ * bound on {@link recordStopCause}'s merge.
91
+ *
92
+ * The merge exists for a single reason: `onError` and `onStop` are two views of the same death,
93
+ * fired by the runtime within moments of each other. Anything older is a DIFFERENT, earlier
94
+ * stop, and merging onto it is not conservative, it is destructive twice over: the new
95
+ * observation inherits the old record's `at`, so it is born already aged, and both halves then
96
+ * fall out of their own attribution windows and explain nothing. A record that is 40 minutes old
97
+ * would silently swallow the crash that just happened.
98
+ *
99
+ * Sized generously against what it models (two hooks, milliseconds apart) and far below the
100
+ * narrowest attribution window, so it cannot be the thing that decides an attribution: what a
101
+ * merge may do is complete an account of one stop, never date it.
102
+ */
103
+ export declare const STOP_MERGE_WINDOW_MS = 60000;
104
+ /** What a container observed about its own stop, once attributed to the job that reads it. */
105
+ export interface StopObservation {
106
+ cause?: ContainerStopCause;
107
+ exit?: ContainerExitState;
108
+ }
52
109
  /**
53
110
  * The message the base container class surfaces when the RUNTIME (not the workload) stopped a
54
111
  * container: a deploy draining the old version. Its own exit-code parser is keyed on the plain
@@ -57,18 +114,43 @@ export declare const ATTRIBUTION_WINDOW_MS: {
57
114
  */
58
115
  export declare function isRolloutSignal(error: unknown): boolean;
59
116
  /**
60
- * The cause a stored record still justifies at `now`, or `undefined` when there is nothing to
61
- * attribute.
117
+ * What a container should record about a stop the runtime just reported, or undefined when it
118
+ * should record nothing.
119
+ *
120
+ * Two rules, both about what the stop MEANS rather than what it says:
121
+ *
122
+ * - A `runtime_signal` 143 is the rollout drain surfacing through this hook instead of (or as
123
+ * well as) `onError`, depending on the runtime version, so it names the same cause.
124
+ * - A stop the container ASKED for records nothing at all. Its idle reclaim and its shutdown RPC
125
+ * both work by signalling the container, so the exit that comes back is this container's own
126
+ * request echoed at it (escalating to a SIGKILL 137 when the workload does not exit inside the
127
+ * grace period), not an observation of how anything died. Recorded anyway, it is read back as
128
+ * the container's cause of death and reported to the operator as "most often an out-of-memory
129
+ * kill" underneath a verdict that says the platform reclaimed an idle container. The cause the
130
+ * caller recorded when it decided to stop is the whole account, and it is already stored.
131
+ */
132
+ export declare function observationForStop(params: {
133
+ exitCode: number;
134
+ reason: 'exit' | 'runtime_signal';
135
+ }, selfInitiated: boolean): StopObservation | undefined;
136
+ /**
137
+ * What a stored record still explains at `now`, or an EMPTY observation when it explains
138
+ * nothing.
62
139
  *
63
- * `undefined` covers three things that all mean the same to the caller: nothing was recorded,
64
- * the record outlived its cause's window (the container recovered and ran on, so a later death
140
+ * An empty observation covers three things that all mean the same to the caller: nothing was
141
+ * recorded, the record outlived its window (the container recovered and ran on, so a later death
65
142
  * is its own), or the record names a cause this build no longer has. The last is possible
66
143
  * because the record is PERSISTED and the vocabulary is closed: a deploy that retires a member
67
144
  * leaves rows behind. All three land on the caller's existing default, reporting the eviction
68
145
  * as a `crash`, which is the honest reading of "no attribution" and the conservative one:
69
146
  * it costs a run one restart of its recovery budget, never a wrongly-extended one.
147
+ *
148
+ * The two halves are attributed INDEPENDENTLY, on their own windows: a record can be too old to
149
+ * excuse the eviction as churn while still being the only account of how the container died, and
150
+ * dropping the exit state with the cause would throw away the diagnostic to protect a budget the
151
+ * diagnostic never touches.
70
152
  */
71
- export declare function attributeStopCause(record: unknown, now: number, claimant: string): ContainerStopCause | undefined;
153
+ export declare function attributeStopCause(record: unknown, now: number, claimant: string): StopObservation;
72
154
  /**
73
155
  * The slice of DO storage the bookkeeping below needs. Narrowed to three methods so the rules
74
156
  * can be exercised against a plain fake. The alternative is asserting them through a real
@@ -79,8 +161,57 @@ export interface StopCauseStorage {
79
161
  put: (key: string, value: StopCauseRecord) => Promise<void>;
80
162
  delete: (key: string) => Promise<unknown>;
81
163
  }
82
- /** Persist what this container just observed about its own reclaim. */
83
- export declare function recordStopCause(storage: StopCauseStorage, cause: ContainerStopCause, now: number): Promise<void>;
164
+ /**
165
+ * Persist what this container just observed about its own stop, MERGING onto an unclaimed record
166
+ * of the SAME stop rather than replacing it.
167
+ *
168
+ * Merging is the point. One stop reaches this container through up to two hooks carrying
169
+ * different halves of the answer: `onError` recognises a rollout drain and knows no exit state,
170
+ * `onStop` carries `{ exitCode, reason }` and cannot name the churn. They fire in either order
171
+ * (and, on some runtime versions, both), so a plain overwrite means whichever landed second
172
+ * silently discarded the other's half: either the recovery budget or the only account of the
173
+ * death, depending on the ordering that day.
174
+ *
175
+ * Two records are never merged:
176
+ *
177
+ * - a CLAIMED one, which has already explained somebody's eviction, so anything observed after
178
+ * it belongs to the next one;
179
+ * - one older than {@link STOP_MERGE_WINDOW_MS}, which is a different stop entirely. This
180
+ * container's records are not reliably cleared between stops (`takeStopCause` deliberately
181
+ * leaves an expired record in place, and only an accepted dispatch deletes one), so a stale
182
+ * record is the ORDINARY state of a container that idled out and was re-driven, not an edge
183
+ * case. Merging onto it would back-date the new observation to the old stop's `at` and age it
184
+ * out of its own attribution window on arrival: the crash that just happened would be
185
+ * recorded, read back, and dropped as too old to explain the eviction it caused.
186
+ *
187
+ * The kept `at` is the EARLIEST of the two observations, which within the merge window is a
188
+ * choice between timestamps moments apart: both describe one stop, and the window measures how
189
+ * long ago that stop happened.
190
+ */
191
+ export declare function recordStopCause(storage: StopCauseStorage, observed: StopObservation, now: number): Promise<void>;
192
+ /**
193
+ * The one-line account of a container's stop for the eviction `detail`, or undefined when
194
+ * nothing was recorded.
195
+ *
196
+ * `cause` is the account the run has ALREADY been given, and passing it is what keeps the detail
197
+ * from arguing with the verdict beside it. The two halves are recorded independently and the
198
+ * exit state is attached whether or not a cause was recognised, so they routinely describe one
199
+ * event twice: an idle reclaim is performed BY the platform with a SIGKILL, so a run correctly
200
+ * reported as "idle container reclaimed between polls" carries exit 137, and read as an
201
+ * unexplained death that code says "most often an out-of-memory kill". The operator is then
202
+ * holding a verdict and a detail that cannot both be true, which is worse than either alone,
203
+ * because there is no way to tell which one to act on.
204
+ *
205
+ * So the interpretation is offered only where it is the only account there is. Where a cause was
206
+ * named, the exit code is reported as the MECHANICS of that stop: still the difference between
207
+ * "reclaimed" and "reclaimed with a SIGKILL", and never a second, competing cause of death.
208
+ *
209
+ * Either way it states the platform's own limit rather than leaving it to be inferred: a
210
+ * Cloudflare Container writes its stdout to the deployment's Workers logs, and nothing inside
211
+ * the Durable Object can read it back, so an operator who reads this and goes looking for a log
212
+ * tail here should be told where the tail actually is.
213
+ */
214
+ export declare function describeContainerExit(exit: ContainerExitState | undefined, cause?: ContainerStopCause): string | undefined;
84
215
  /**
85
216
  * Forget whatever this container observed before now.
86
217
  *
@@ -93,13 +224,17 @@ export declare function recordStopCause(storage: StopCauseStorage, cause: Contai
93
224
  */
94
225
  export declare function clearStopCause(storage: StopCauseStorage): Promise<void>;
95
226
  /**
96
- * Read the cause explaining `claimant`'s 404 poll, CLAIMING it for that job.
227
+ * Read what explains `claimant`'s 404 poll, CLAIMING it for that job.
97
228
  *
98
- * One reclaim explains exactly one job's eviction: the engine answers one by re-dispatching onto
229
+ * One stop explains exactly one job's eviction: the engine answers one by re-dispatching onto
99
230
  * a fresh container under the same DO id, so an unclaimed record would still be sitting there to
100
231
  * excuse the next death, whenever and for whatever reason it came. The claim is written rather
101
232
  * than the record deleted so a REPLAYED poll for the same job reads back the same answer (see
102
233
  * {@link StopCauseRecord.claimedBy}).
234
+ *
235
+ * The claim covers the record, not one half of it: an exit state read here is spent as surely as
236
+ * a cause is, because attaching one container's death to two runs' failures is the same lie
237
+ * either way.
103
238
  */
104
- export declare function takeStopCause(storage: StopCauseStorage, now: number, claimant: string): Promise<ContainerStopCause | undefined>;
239
+ export declare function takeStopCause(storage: StopCauseStorage, now: number, claimant: string): Promise<StopObservation>;
105
240
  //# sourceMappingURL=stopCause.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stopCause.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/containers/stopCause.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAA;AAEnD,gGAAgG;AAChG,eAAO,MAAM,cAAc,uBAAuB,CAAA;AAElD,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,kBAAkB,CAAA;IACzB,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAA;IACV;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB;;;CAGY,CAAA;AAE9C;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIvD;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,kBAAkB,GAAG,SAAS,CAQhC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC1C;AAED,uEAAuE;AACvE,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CASzC"}
1
+ {"version":3,"file":"stopCause.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/containers/stopCause.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAA;AAEnD,gGAAgG;AAChG,eAAO,MAAM,cAAc,uBAAuB,CAAA;AAElD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAClC;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAA;IAC1B,mFAAmF;IACnF,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAA;IACV;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB;;;CAGY,CAAA;AAE9C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,QAAc,CAAA;AAErD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,QAAS,CAAA;AAE1C,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,kBAAkB,CAAA;IAC1B,IAAI,CAAC,EAAE,kBAAkB,CAAA;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIvD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,EAC/D,aAAa,EAAE,OAAO,GACrB,eAAe,GAAG,SAAS,CAO7B;AAwBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,eAAe,CAYjB;AAOD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAUf;AAmDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,GAAG,SAAS,EACpC,KAAK,CAAC,EAAE,kBAAkB,GACzB,MAAM,GAAG,SAAS,CAgBpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC,CAS1B"}
@@ -26,6 +26,37 @@ export const ATTRIBUTION_WINDOW_MS = {
26
26
  rollout: 120_000,
27
27
  idle: 30 * 60_000,
28
28
  };
29
+ /**
30
+ * How long a stop this container could NOT attribute (a crash, an OOM kill) may still explain a
31
+ * 404 poll, i.e. the window governing a record that carries only an {@link ContainerExitState}.
32
+ *
33
+ * The same reasoning as the `idle` window, and deliberately the same number: what separates the
34
+ * death from the poll that discovers it is the POLL GAP, and a crash is discovered by the same
35
+ * ~15s tick an idle reclaim is, stretched by whatever delayed the driver. Sizing this to the
36
+ * rollout case instead would read every crash discovered after a re-drive as unexplained, which
37
+ * is the class this record exists to explain.
38
+ *
39
+ * Wide is affordable here for a reason the cause windows do not share: an exit state changes no
40
+ * verdict. It is attached to the failure `detail`, so the cost of over-attributing is a
41
+ * misleading sentence, never a wrongly-extended recovery budget.
42
+ */
43
+ export const EXIT_ATTRIBUTION_WINDOW_MS = 30 * 60_000;
44
+ /**
45
+ * How far apart two hook calls may be and still be treated as observations of ONE stop, i.e. the
46
+ * bound on {@link recordStopCause}'s merge.
47
+ *
48
+ * The merge exists for a single reason: `onError` and `onStop` are two views of the same death,
49
+ * fired by the runtime within moments of each other. Anything older is a DIFFERENT, earlier
50
+ * stop, and merging onto it is not conservative, it is destructive twice over: the new
51
+ * observation inherits the old record's `at`, so it is born already aged, and both halves then
52
+ * fall out of their own attribution windows and explain nothing. A record that is 40 minutes old
53
+ * would silently swallow the crash that just happened.
54
+ *
55
+ * Sized generously against what it models (two hooks, milliseconds apart) and far below the
56
+ * narrowest attribution window, so it cannot be the thing that decides an attribution: what a
57
+ * merge may do is complete an account of one stop, never date it.
58
+ */
59
+ export const STOP_MERGE_WINDOW_MS = 60_000;
29
60
  /**
30
61
  * The message the base container class surfaces when the RUNTIME (not the workload) stopped a
31
62
  * container: a deploy draining the old version. Its own exit-code parser is keyed on the plain
@@ -36,6 +67,31 @@ export function isRolloutSignal(error) {
36
67
  const message = error instanceof Error ? error.message : typeof error === 'string' ? error : String(error);
37
68
  return /new version rollout|runtime signalled the container to exit/i.test(message);
38
69
  }
70
+ /**
71
+ * What a container should record about a stop the runtime just reported, or undefined when it
72
+ * should record nothing.
73
+ *
74
+ * Two rules, both about what the stop MEANS rather than what it says:
75
+ *
76
+ * - A `runtime_signal` 143 is the rollout drain surfacing through this hook instead of (or as
77
+ * well as) `onError`, depending on the runtime version, so it names the same cause.
78
+ * - A stop the container ASKED for records nothing at all. Its idle reclaim and its shutdown RPC
79
+ * both work by signalling the container, so the exit that comes back is this container's own
80
+ * request echoed at it (escalating to a SIGKILL 137 when the workload does not exit inside the
81
+ * grace period), not an observation of how anything died. Recorded anyway, it is read back as
82
+ * the container's cause of death and reported to the operator as "most often an out-of-memory
83
+ * kill" underneath a verdict that says the platform reclaimed an idle container. The cause the
84
+ * caller recorded when it decided to stop is the whole account, and it is already stored.
85
+ */
86
+ export function observationForStop(params, selfInitiated) {
87
+ if (selfInitiated)
88
+ return undefined;
89
+ const rollout = params.reason === 'runtime_signal' && params.exitCode === 143;
90
+ return {
91
+ ...(rollout ? { cause: 'rollout' } : {}),
92
+ exit: { code: params.exitCode, reason: params.reason },
93
+ };
94
+ }
39
95
  /**
40
96
  * Whether a value read back out of DO storage is still a cause this build knows. Derived from
41
97
  * the window table's own keys rather than restated, so retiring a cause fails the build here
@@ -45,32 +101,177 @@ function isContainerStopCause(value) {
45
101
  return typeof value === 'string' && Object.hasOwn(ATTRIBUTION_WINDOW_MS, value);
46
102
  }
47
103
  /**
48
- * The cause a stored record still justifies at `now`, or `undefined` when there is nothing to
49
- * attribute.
104
+ * Whether a value read back out of DO storage is an exit state this build can render.
50
105
  *
51
- * `undefined` covers three things that all mean the same to the caller: nothing was recorded,
52
- * the record outlived its cause's window (the container recovered and ran on, so a later death
106
+ * The `reason` union is closed and PERSISTED, so an unknown member is possible after a runtime
107
+ * bump; it degrades to "no exit state recorded" rather than being rendered as a bare string,
108
+ * because a reason nobody has worded says less than the exit code alone already does.
109
+ */
110
+ function isContainerExitState(value) {
111
+ if (!value || typeof value !== 'object')
112
+ return false;
113
+ const { code, reason } = value;
114
+ return typeof code === 'number' && (reason === 'exit' || reason === 'runtime_signal');
115
+ }
116
+ /**
117
+ * What a stored record still explains at `now`, or an EMPTY observation when it explains
118
+ * nothing.
119
+ *
120
+ * An empty observation covers three things that all mean the same to the caller: nothing was
121
+ * recorded, the record outlived its window (the container recovered and ran on, so a later death
53
122
  * is its own), or the record names a cause this build no longer has. The last is possible
54
123
  * because the record is PERSISTED and the vocabulary is closed: a deploy that retires a member
55
124
  * leaves rows behind. All three land on the caller's existing default, reporting the eviction
56
125
  * as a `crash`, which is the honest reading of "no attribution" and the conservative one:
57
126
  * it costs a run one restart of its recovery budget, never a wrongly-extended one.
127
+ *
128
+ * The two halves are attributed INDEPENDENTLY, on their own windows: a record can be too old to
129
+ * excuse the eviction as churn while still being the only account of how the container died, and
130
+ * dropping the exit state with the cause would throw away the diagnostic to protect a budget the
131
+ * diagnostic never touches.
58
132
  */
59
133
  export function attributeStopCause(record, now, claimant) {
60
134
  if (!record || typeof record !== 'object')
61
- return undefined;
62
- const { cause, at, claimedBy } = record;
63
- if (!isContainerStopCause(cause) || typeof at !== 'number')
64
- return undefined;
135
+ return {};
136
+ const { cause, exit, at, claimedBy } = record;
137
+ if (typeof at !== 'number')
138
+ return {};
65
139
  // Spent on somebody else's eviction. Only the job that claimed it may read it back, which is
66
140
  // what lets a replay be idempotent without letting one reclaim excuse two deaths.
67
141
  if (claimedBy !== undefined && claimedBy !== claimant)
142
+ return {};
143
+ const age = now - at;
144
+ return {
145
+ ...(isContainerStopCause(cause) && age <= ATTRIBUTION_WINDOW_MS[cause] ? { cause } : {}),
146
+ ...(isContainerExitState(exit) && age <= EXIT_ATTRIBUTION_WINDOW_MS ? { exit } : {}),
147
+ };
148
+ }
149
+ /** Whether an observation carries anything at all (i.e. whether claiming it is worth a write). */
150
+ function isEmptyObservation(observation) {
151
+ return !observation.cause && !observation.exit;
152
+ }
153
+ /**
154
+ * Persist what this container just observed about its own stop, MERGING onto an unclaimed record
155
+ * of the SAME stop rather than replacing it.
156
+ *
157
+ * Merging is the point. One stop reaches this container through up to two hooks carrying
158
+ * different halves of the answer: `onError` recognises a rollout drain and knows no exit state,
159
+ * `onStop` carries `{ exitCode, reason }` and cannot name the churn. They fire in either order
160
+ * (and, on some runtime versions, both), so a plain overwrite means whichever landed second
161
+ * silently discarded the other's half: either the recovery budget or the only account of the
162
+ * death, depending on the ordering that day.
163
+ *
164
+ * Two records are never merged:
165
+ *
166
+ * - a CLAIMED one, which has already explained somebody's eviction, so anything observed after
167
+ * it belongs to the next one;
168
+ * - one older than {@link STOP_MERGE_WINDOW_MS}, which is a different stop entirely. This
169
+ * container's records are not reliably cleared between stops (`takeStopCause` deliberately
170
+ * leaves an expired record in place, and only an accepted dispatch deletes one), so a stale
171
+ * record is the ORDINARY state of a container that idled out and was re-driven, not an edge
172
+ * case. Merging onto it would back-date the new observation to the old stop's `at` and age it
173
+ * out of its own attribution window on arrival: the crash that just happened would be
174
+ * recorded, read back, and dropped as too old to explain the eviction it caused.
175
+ *
176
+ * The kept `at` is the EARLIEST of the two observations, which within the merge window is a
177
+ * choice between timestamps moments apart: both describe one stop, and the window measures how
178
+ * long ago that stop happened.
179
+ */
180
+ export async function recordStopCause(storage, observed, now) {
181
+ const mergeable = mergeableRecord(await storage.get(STOP_CAUSE_KEY), now);
182
+ const at = mergeable ? Math.min(mergeable.at, now) : now;
183
+ const cause = observed.cause ?? mergeable?.cause;
184
+ const exit = observed.exit ?? mergeable?.exit;
185
+ await storage.put(STOP_CAUSE_KEY, {
186
+ ...(cause ? { cause } : {}),
187
+ ...(exit ? { exit } : {}),
188
+ at,
189
+ });
190
+ }
191
+ /**
192
+ * The stored record a new observation may complete, i.e. an unclaimed one describing the same
193
+ * stop. Anything else (absent, malformed, claimed, or from an earlier stop) is undefined, and
194
+ * the new observation stands alone.
195
+ */
196
+ function mergeableRecord(stored, now) {
197
+ if (!stored || typeof stored !== 'object')
198
+ return undefined;
199
+ const record = stored;
200
+ if (record.claimedBy !== undefined)
68
201
  return undefined;
69
- return now - at <= ATTRIBUTION_WINDOW_MS[cause] ? cause : undefined;
202
+ if (typeof record.at !== 'number')
203
+ return undefined;
204
+ // `now - at` rather than an absolute difference: a record stamped in the FUTURE (a clock
205
+ // adjustment between two hook calls) is one of ours moments ago, not a stale one.
206
+ if (now - record.at > STOP_MERGE_WINDOW_MS)
207
+ return undefined;
208
+ return record;
70
209
  }
71
- /** Persist what this container just observed about its own reclaim. */
72
- export async function recordStopCause(storage, cause, now) {
73
- await storage.put(STOP_CAUSE_KEY, { cause, at: now });
210
+ /**
211
+ * What an exit code IS, independent of why the container stopped.
212
+ *
213
+ * A bounded map rather than a `128 + n` derivation on purpose: the platform reports one number
214
+ * and only a couple of them are worth translating, while decoding an arbitrary code into a
215
+ * signal name would confidently mislabel an application exit code that merely happens to exceed
216
+ * 128. An unmapped code is reported as itself.
217
+ */
218
+ const EXIT_SIGNAL_NAMES = {
219
+ 137: 'SIGKILL',
220
+ 143: 'SIGTERM',
221
+ };
222
+ /**
223
+ * What an exit code SUGGESTS about a death nothing else explains. Read only when no
224
+ * {@link ContainerStopCause} was recognised, which is what makes the guess worth offering: the
225
+ * alternative there is silence.
226
+ *
227
+ * Kept apart from {@link EXIT_SIGNAL_NAMES} because the two are different kinds of claim. The
228
+ * signal name is a fact about the number; this is an inference from the absence of any other
229
+ * account, and it is false exactly when that absence is false.
230
+ */
231
+ const UNEXPLAINED_EXIT_NOTES = {
232
+ // Killed outright rather than exiting. With nothing else to explain it, on this runtime that
233
+ // is overwhelmingly the instance running out of memory, which is why it is worth saying.
234
+ 137: 'most often an out-of-memory kill or a forced stop',
235
+ // Something asked it to stop, and whatever it was left no cause behind for us to name.
236
+ 143: 'something asked the container to stop',
237
+ };
238
+ /**
239
+ * The one-line account of a container's stop for the eviction `detail`, or undefined when
240
+ * nothing was recorded.
241
+ *
242
+ * `cause` is the account the run has ALREADY been given, and passing it is what keeps the detail
243
+ * from arguing with the verdict beside it. The two halves are recorded independently and the
244
+ * exit state is attached whether or not a cause was recognised, so they routinely describe one
245
+ * event twice: an idle reclaim is performed BY the platform with a SIGKILL, so a run correctly
246
+ * reported as "idle container reclaimed between polls" carries exit 137, and read as an
247
+ * unexplained death that code says "most often an out-of-memory kill". The operator is then
248
+ * holding a verdict and a detail that cannot both be true, which is worse than either alone,
249
+ * because there is no way to tell which one to act on.
250
+ *
251
+ * So the interpretation is offered only where it is the only account there is. Where a cause was
252
+ * named, the exit code is reported as the MECHANICS of that stop: still the difference between
253
+ * "reclaimed" and "reclaimed with a SIGKILL", and never a second, competing cause of death.
254
+ *
255
+ * Either way it states the platform's own limit rather than leaving it to be inferred: a
256
+ * Cloudflare Container writes its stdout to the deployment's Workers logs, and nothing inside
257
+ * the Durable Object can read it back, so an operator who reads this and goes looking for a log
258
+ * tail here should be told where the tail actually is.
259
+ */
260
+ export function describeContainerExit(exit, cause) {
261
+ if (!exit)
262
+ return undefined;
263
+ const signal = EXIT_SIGNAL_NAMES[exit.code];
264
+ const note = cause
265
+ ? signal
266
+ : [signal, UNEXPLAINED_EXIT_NOTES[exit.code]].filter(Boolean).join(', ');
267
+ const how = cause
268
+ ? 'the runtime reported it as'
269
+ : exit.reason === 'runtime_signal'
270
+ ? 'the runtime signalled it to exit, with'
271
+ : 'the workload inside it exited, with';
272
+ return (`The run's container stopped while the job was running: ${how} exit code ${exit.code}` +
273
+ `${note ? ` (${note})` : ''}. The container's own output is not readable from the Worker; ` +
274
+ `it is in this deployment's Workers logs.`);
74
275
  }
75
276
  /**
76
277
  * Forget whatever this container observed before now.
@@ -86,23 +287,27 @@ export async function clearStopCause(storage) {
86
287
  await storage.delete(STOP_CAUSE_KEY);
87
288
  }
88
289
  /**
89
- * Read the cause explaining `claimant`'s 404 poll, CLAIMING it for that job.
290
+ * Read what explains `claimant`'s 404 poll, CLAIMING it for that job.
90
291
  *
91
- * One reclaim explains exactly one job's eviction: the engine answers one by re-dispatching onto
292
+ * One stop explains exactly one job's eviction: the engine answers one by re-dispatching onto
92
293
  * a fresh container under the same DO id, so an unclaimed record would still be sitting there to
93
294
  * excuse the next death, whenever and for whatever reason it came. The claim is written rather
94
295
  * than the record deleted so a REPLAYED poll for the same job reads back the same answer (see
95
296
  * {@link StopCauseRecord.claimedBy}).
297
+ *
298
+ * The claim covers the record, not one half of it: an exit state read here is spent as surely as
299
+ * a cause is, because attaching one container's death to two runs' failures is the same lie
300
+ * either way.
96
301
  */
97
302
  export async function takeStopCause(storage, now, claimant) {
98
303
  const record = await storage.get(STOP_CAUSE_KEY);
99
- const cause = attributeStopCause(record, now, claimant);
304
+ const observation = attributeStopCause(record, now, claimant);
100
305
  // Nothing to claim: either there was no record, it was spent, or it is too old to explain
101
306
  // anything. Leaving an expired record in place costs one key until the next reclaim
102
307
  // overwrites it, and rewriting it here would only re-date somebody else's observation.
103
- if (!cause)
104
- return undefined;
308
+ if (isEmptyObservation(observation))
309
+ return observation;
105
310
  await storage.put(STOP_CAUSE_KEY, { ...record, claimedBy: claimant });
106
- return cause;
311
+ return observation;
107
312
  }
108
313
  //# sourceMappingURL=stopCause.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stopCause.js","sourceRoot":"","sources":["../../../src/infrastructure/containers/stopCause.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,yFAAyF;AACzF,uFAAuF;AACvF,yFAAyF;AACzF,4FAA4F;AAC5F,8CAA8C;AAe9C,gGAAgG;AAChG,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAA;AAoBlD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,EAAE,GAAG,MAAM;CAC2B,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC5F,OAAO,8DAA8D,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AACrF,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAA;AACjF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAe,EACf,GAAW,EACX,QAAgB;IAEhB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC3D,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,MAAkC,CAAA;IACnE,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC5E,6FAA6F;IAC7F,kFAAkF;IAClF,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACvE,OAAO,GAAG,GAAG,EAAE,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACrE,CAAC;AAaD,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAyB,EACzB,KAAyB,EACzB,GAAW;IAEX,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAyB;IAC5D,MAAM,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAyB,EACzB,GAAW,EACX,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAChD,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;IACvD,0FAA0F;IAC1F,oFAAoF;IACpF,uFAAuF;IACvF,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,MAAM,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAI,MAA0B,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC1F,OAAO,KAAK,CAAA;AACd,CAAC"}
1
+ {"version":3,"file":"stopCause.js","sourceRoot":"","sources":["../../../src/infrastructure/containers/stopCause.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export declare class D1BinaryArtifactMetadataStore implements BinaryArtifactMeta
12
12
  countByExecution(workspaceId: string, executionId: string): Promise<number>;
13
13
  listByBlock(workspaceId: string, blockId: string): Promise<BinaryArtifactRecord[]>;
14
14
  listByDocument(workspaceId: string, document: DocumentArtifactRef): Promise<BinaryArtifactRecord[]>;
15
+ listByDocuments(workspaceId: string, documents: readonly DocumentArtifactRef[]): Promise<BinaryArtifactRecord[]>;
15
16
  deleteByIds(workspaceId: string, ids: readonly string[]): Promise<number>;
16
17
  delete(workspaceId: string, id: string): Promise<void>;
17
18
  listOlderThan(workspaceId: string, olderThan: number): Promise<BinaryArtifactRecord[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"D1BinaryArtifactMetadataStore.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/repositories/D1BinaryArtifactMetadataStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AA2C3D,+FAA+F;AAC/F,qBAAa,6BAA8B,YAAW,2BAA2B;IAC/E,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAE/B,YAAY,EAAE,EAAE,EAAE,EAAE;QAAE,EAAE,EAAE,UAAU,CAAA;KAAE,EAErC;IAEK,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BxD;IAEK,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAM/E;IAEK,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAU/F;IAEK,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhF;IAEK,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAUvF;IAEK,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAUjC;IAEK,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAc9E;IAEK,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3D;IAIK,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAS3F;IAEK,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS7E;IAEK,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAM1E;IAEK,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5D;CACF"}
1
+ {"version":3,"file":"D1BinaryArtifactMetadataStore.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/repositories/D1BinaryArtifactMetadataStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAqD3D,+FAA+F;AAC/F,qBAAa,6BAA8B,YAAW,2BAA2B;IAC/E,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAE/B,YAAY,EAAE,EAAE,EAAE,EAAE;QAAE,EAAE,EAAE,UAAU,CAAA;KAAE,EAErC;IAEK,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BxD;IAEK,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAM/E;IAEK,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAU/F;IAEK,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhF;IAEK,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAUvF;IAEK,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAUjC;IAEK,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,SAAS,mBAAmB,EAAE,GACxC,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAoBjC;IAEK,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAc9E;IAEK,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3D;IAIK,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAS3F;IAEK,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS7E;IAEK,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAM1E;IAEK,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5D;CACF"}
@@ -1,3 +1,4 @@
1
+ import { dedupeDocumentRefs } from '@cat-factory/kernel';
1
2
  import { chunkForIn } from './chunk';
2
3
  function rowToRecord(row) {
3
4
  return {
@@ -20,6 +21,13 @@ function rowToRecord(row) {
20
21
  createdAt: row.created_at,
21
22
  };
22
23
  }
24
+ /**
25
+ * Order rows the way every list read on this table returns them (`created_at`, then `id`), for a
26
+ * batch read whose result is the concatenation of several statements.
27
+ */
28
+ function sortArtifactRows(rows) {
29
+ return rows.sort((a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
30
+ }
23
31
  /** D1-backed metadata store for binary artifacts (see migration 0017). Bytes live in R2/S3. */
24
32
  export class D1BinaryArtifactMetadataStore {
25
33
  db;
@@ -77,6 +85,28 @@ export class D1BinaryArtifactMetadataStore {
77
85
  .all();
78
86
  return (results ?? []).map(rowToRecord);
79
87
  }
88
+ async listByDocuments(workspaceId, documents) {
89
+ const refs = dedupeDocumentRefs(documents);
90
+ if (!refs.length)
91
+ return [];
92
+ const rows = [];
93
+ // Two bound params per ref (source + external id), so the chunk size is derived from that
94
+ // rather than from the scalar-`IN` default.
95
+ for (const chunk of chunkForIn(refs, 2)) {
96
+ const clauses = chunk
97
+ .map(() => '(document_source = ? AND document_external_id = ?)')
98
+ .join(' OR ');
99
+ const { results } = await this.db
100
+ .prepare(`SELECT * FROM binary_artifacts WHERE workspace_id = ? AND (${clauses})`)
101
+ .bind(workspaceId, ...chunk.flatMap((ref) => [ref.source, ref.externalId]))
102
+ .all();
103
+ rows.push(...(results ?? []));
104
+ }
105
+ // Sorted here rather than per statement: with more than one chunk the concatenation is
106
+ // ordered by chunk, and the caller's "newest render for a view wins" rule reads the WHOLE
107
+ // list in order.
108
+ return sortArtifactRows(rows).map(rowToRecord);
109
+ }
80
110
  async deleteByIds(workspaceId, ids) {
81
111
  let removed = 0;
82
112
  for (const chunk of chunkForIn(ids)) {
@@ -1 +1 @@
1
- {"version":3,"file":"D1BinaryArtifactMetadataStore.js","sourceRoot":"","sources":["../../../src/infrastructure/repositories/D1BinaryArtifactMetadataStore.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"D1BinaryArtifactMetadataStore.js","sourceRoot":"","sources":["../../../src/infrastructure/repositories/D1BinaryArtifactMetadataStore.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,11 @@
1
- /** Split `items` into consecutive chunks no larger than {@link D1_MAX_IN_PARAMS}. */
2
- export declare function chunkForIn<T>(items: readonly T[]): T[][];
1
+ /**
2
+ * Split `items` into consecutive chunks that stay under {@link D1_MAX_IN_PARAMS} bound
3
+ * parameters.
4
+ *
5
+ * `paramsPerItem` is how many parameters ONE item contributes: 1 for a plain `id IN (…)` list,
6
+ * 2 for a composite key matched as `(a = ? AND b = ?) OR …`. It is a divisor rather than a
7
+ * caller-picked chunk size so the ceiling stays stated in the unit D1 actually enforces, and a
8
+ * composite-key list cannot silently inherit a chunk size that was only safe for scalars.
9
+ */
10
+ export declare function chunkForIn<T>(items: readonly T[], paramsPerItem?: number): T[][];
3
11
  //# sourceMappingURL=chunk.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/repositories/chunk.ts"],"names":[],"mappings":"AASA,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAMxD"}
1
+ {"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/repositories/chunk.ts"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,aAAa,SAAI,GAAG,CAAC,EAAE,EAAE,CAO3E"}
@@ -6,11 +6,20 @@
6
6
  * list (e.g. a leading `github_id = ?`).
7
7
  */
8
8
  const D1_MAX_IN_PARAMS = 90;
9
- /** Split `items` into consecutive chunks no larger than {@link D1_MAX_IN_PARAMS}. */
10
- export function chunkForIn(items) {
9
+ /**
10
+ * Split `items` into consecutive chunks that stay under {@link D1_MAX_IN_PARAMS} bound
11
+ * parameters.
12
+ *
13
+ * `paramsPerItem` is how many parameters ONE item contributes: 1 for a plain `id IN (…)` list,
14
+ * 2 for a composite key matched as `(a = ? AND b = ?) OR …`. It is a divisor rather than a
15
+ * caller-picked chunk size so the ceiling stays stated in the unit D1 actually enforces, and a
16
+ * composite-key list cannot silently inherit a chunk size that was only safe for scalars.
17
+ */
18
+ export function chunkForIn(items, paramsPerItem = 1) {
19
+ const perChunk = Math.max(1, Math.floor(D1_MAX_IN_PARAMS / Math.max(1, paramsPerItem)));
11
20
  const chunks = [];
12
- for (let i = 0; i < items.length; i += D1_MAX_IN_PARAMS) {
13
- chunks.push(items.slice(i, i + D1_MAX_IN_PARAMS));
21
+ for (let i = 0; i < items.length; i += perChunk) {
22
+ chunks.push(items.slice(i, i + perChunk));
14
23
  }
15
24
  return chunks;
16
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../../src/infrastructure/repositories/chunk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAI,KAAmB;IAC/C,MAAM,MAAM,GAAU,EAAE,CAAA;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,gBAAgB,EAAE,CAAC;QACxD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../../src/infrastructure/repositories/chunk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAI,KAAmB,EAAE,aAAa,GAAG,CAAC;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IACvF,MAAM,MAAM,GAAU,EAAE,CAAA;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAA;IAC3C,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/worker",
3
- "version": "0.176.0",
3
+ "version": "0.177.0",
4
4
  "description": "Reusable Cloudflare Worker library (Hono + D1) exposing the Agent Architecture Board core: the `createApp()` Hono factory, the default fetch/scheduled/queue handler, and the Durable Object + Workflow classes. Deployments (see deploy/backend) supply the wrangler.toml + config and re-export these.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,22 +43,22 @@
43
43
  "pino": "^10.3.1",
44
44
  "valibot": "^1.4.2",
45
45
  "workers-ai-provider": "^4.0.0",
46
- "@cat-factory/agents": "0.117.11",
47
- "@cat-factory/consensus": "0.14.57",
48
- "@cat-factory/contracts": "0.273.0",
49
- "@cat-factory/eks": "0.1.269",
50
- "@cat-factory/caching": "0.18.12",
51
- "@cat-factory/gates": "0.9.37",
52
- "@cat-factory/gitlab": "0.18.4",
53
- "@cat-factory/integrations": "0.149.0",
54
- "@cat-factory/kernel": "0.271.0",
55
- "@cat-factory/observability-langfuse": "0.10.41",
56
- "@cat-factory/orchestration": "0.239.0",
57
- "@cat-factory/prompt-fragments": "1.0.21",
58
- "@cat-factory/provider-cloudflare": "0.7.419",
59
- "@cat-factory/spend": "0.15.39",
60
- "@cat-factory/server": "0.251.0",
61
- "@cat-factory/observability-otel": "0.18.4"
46
+ "@cat-factory/agents": "0.117.12",
47
+ "@cat-factory/caching": "0.18.13",
48
+ "@cat-factory/consensus": "0.14.58",
49
+ "@cat-factory/contracts": "0.274.0",
50
+ "@cat-factory/eks": "0.1.270",
51
+ "@cat-factory/gates": "0.9.38",
52
+ "@cat-factory/gitlab": "0.18.5",
53
+ "@cat-factory/integrations": "0.150.0",
54
+ "@cat-factory/kernel": "0.272.0",
55
+ "@cat-factory/observability-langfuse": "0.10.42",
56
+ "@cat-factory/observability-otel": "0.18.5",
57
+ "@cat-factory/orchestration": "0.240.0",
58
+ "@cat-factory/prompt-fragments": "1.0.22",
59
+ "@cat-factory/provider-cloudflare": "0.7.420",
60
+ "@cat-factory/server": "0.252.0",
61
+ "@cat-factory/spend": "0.15.40"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@cloudflare/vitest-pool-workers": "^0.20.1",
@@ -66,7 +66,7 @@
66
66
  "typescript": "7.0.2",
67
67
  "vitest": "^4.1.10",
68
68
  "wrangler": "^4.118.0",
69
- "@cat-factory/conformance": "0.34.0"
69
+ "@cat-factory/conformance": "0.35.0"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsc -b tsconfig.build.json",