@tokenoftrust/storefront-runner 2.4.7 → 2.4.9

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.
@@ -0,0 +1,78 @@
1
+ // The workerd pool both pipeline suites run in (@cloudflare/vitest-pool-workers): the TenantPipeline
2
+ // Durable Object is exercised with real SQLite storage, real alarms and real eviction
3
+ // (`ctx.abort()`). The Node unit suite (vitest.config.cts) never picks these tests up.
4
+ //
5
+ // The object lives in its own Worker, `pipeline-host`, not in the test runner's isolate: an
6
+ // eviction must restart the object without touching its callers, as in production. Auxiliary
7
+ // Workers take plain modules, so the host is bundled here, in memory, with esbuild.
8
+ //
9
+ // vitest.workers.config.cts test/pipeline/** the hand-written harness tests, one at a time
10
+ // vitest.faults.config.cts test/pipeline-faults/** the systematic fault matrix, cells concurrent
11
+ const { defineWorkersConfig } = require("@cloudflare/vitest-pool-workers/config");
12
+ const esbuild = require("esbuild");
13
+ const path = require("node:path");
14
+
15
+ const compatibilityDate = "2024-11-01";
16
+ const compatibilityFlags = ["nodejs_compat"];
17
+ const HOST = "pipeline-host";
18
+
19
+ /**
20
+ * @param {{ root: string; name: string; include: string[]; maxConcurrency?: number; testTimeout?: number }} options
21
+ */
22
+ function pipelinePoolConfig({ root, name, include, maxConcurrency, testTimeout = 60_000 }) {
23
+ const hostModule = esbuild.buildSync({
24
+ entryPoints: [path.join(root, "test/pipeline/pipeline-host.ts")],
25
+ bundle: true,
26
+ format: "esm",
27
+ platform: "neutral",
28
+ target: "es2022",
29
+ external: ["cloudflare:*"],
30
+ write: false,
31
+ }).outputFiles[0].text;
32
+
33
+ const pipelineBinding = { TENANT_PIPELINE: { className: "TenantPipeline", scriptName: HOST } };
34
+
35
+ return defineWorkersConfig({
36
+ // The source tree's `@/` import alias (tsconfig `paths`), for modules the tests import directly.
37
+ resolve: { alias: { "@": path.join(root, "src") } },
38
+ test: {
39
+ name,
40
+ include,
41
+ cache: false,
42
+ testTimeout,
43
+ hookTimeout: 60_000,
44
+ ...(maxConcurrency ? { maxConcurrency } : {}),
45
+ poolOptions: {
46
+ workers: {
47
+ isolatedStorage: false,
48
+ singleWorker: true,
49
+ miniflare: {
50
+ compatibilityDate,
51
+ compatibilityFlags,
52
+ durableObjects: pipelineBinding,
53
+ serviceBindings: { PIPELINE_HOST: HOST, INTAKE_B: "pipeline-intake-b" },
54
+ workers: [
55
+ {
56
+ name: HOST,
57
+ modules: [{ type: "ESModule", path: "pipeline-host.mjs", contents: hostModule }],
58
+ compatibilityDate,
59
+ compatibilityFlags,
60
+ durableObjects: { TENANT_PIPELINE: { className: "TenantPipeline", useSQLite: true } },
61
+ },
62
+ {
63
+ name: "pipeline-intake-b",
64
+ modules: true,
65
+ scriptPath: path.join(root, "test/pipeline/intake-worker.mjs"),
66
+ compatibilityDate,
67
+ compatibilityFlags,
68
+ durableObjects: pipelineBinding,
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ },
74
+ },
75
+ });
76
+ }
77
+
78
+ module.exports = { pipelinePoolConfig };
@@ -26,18 +26,27 @@ import {
26
26
  movePreview,
27
27
  standInPublishEnvironment,
28
28
  testVersionStore,
29
+ type CiDispatch,
29
30
  type TestKv,
31
+ type WorldDesignation,
30
32
  } from "../../src/lib/pipeline/publish/publish-test-support";
33
+ import type { Landed } from "./matrix-faults";
31
34
 
32
35
  type SiteVersionStore = ReturnType<typeof testVersionStore>;
33
36
  type PointerWriter = NonNullable<Parameters<typeof testVersionStore>[1]>;
37
+ type PointerRecord = Awaited<ReturnType<PointerWriter["read"]>>;
34
38
 
39
+ /**
40
+ * Production's ratios, in seconds: a CI window (45 min) outlasts the lease an evicted object waits
41
+ * out before it resumes (10 min, here `leaseMs`), so a job resumed after an eviction is still inside
42
+ * its window — as in production.
43
+ */
35
44
  export const PUBLISH_HARNESS_TIMING: PublishTiming = {
36
- awaitWindowMs: 1_500,
45
+ awaitWindowMs: 4_000,
37
46
  runGraceMs: 500,
38
47
  proofDelayMs: 30,
39
48
  proofProbes: { synchronous: 3, asynchronous: 5 },
40
- stageWindowMs: 1_500,
49
+ stageWindowMs: 4_000,
41
50
  stageChecks: 3,
42
51
  };
43
52
 
@@ -64,25 +73,6 @@ function tables(state: DurableObjectState): void {
64
73
  sql.exec(`CREATE TABLE IF NOT EXISTS harness_kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)`);
65
74
  sql.exec(`CREATE TABLE IF NOT EXISTS harness_publish_faults (id INTEGER PRIMARY KEY AUTOINCREMENT, step TEXT, at TEXT, mode TEXT, remaining INTEGER, version_id TEXT)`);
66
75
  sql.exec(`CREATE TABLE IF NOT EXISTS harness_publish_fired (seq INTEGER PRIMARY KEY AUTOINCREMENT, step TEXT, at TEXT, mode TEXT)`);
67
- sql.exec(`CREATE TABLE IF NOT EXISTS harness_publish_armed (seq INTEGER PRIMARY KEY AUTOINCREMENT, step TEXT, at TEXT, armed INTEGER)`);
68
- }
69
-
70
- /** At each fired fault: how many compensations the engine held armed for jobs not yet terminal. */
71
- export function armedAtFaults(state: DurableObjectState): { step: string; at: string; armed: number }[] {
72
- tables(state);
73
- return state.storage.sql.exec<{ step: string; at: string; armed: number }>(`SELECT step, at, armed FROM harness_publish_armed ORDER BY seq`).toArray();
74
- }
75
-
76
- function armedCompensations(state: DurableObjectState): number {
77
- try {
78
- return state.storage.sql
79
- .exec<{ n: number }>(
80
- `SELECT COUNT(*) AS n FROM terminal_hooks h JOIN jobs j ON j.job_id = h.job_id WHERE j.state NOT IN ('succeeded', 'needs-developer', 'stuck')`,
81
- )
82
- .one().n;
83
- } catch {
84
- return -1;
85
- }
86
76
  }
87
77
 
88
78
  /** A KV over the object's SQLite: durable across evictions, visible to every later execution. */
@@ -133,7 +123,6 @@ async function maybeFault(state: DurableObjectState, step: string, at: "before"
133
123
  if (!fault) return;
134
124
  sql.exec(`UPDATE harness_publish_faults SET remaining = remaining - 1 WHERE id = ?`, fault.id);
135
125
  sql.exec(`INSERT INTO harness_publish_fired (step, at, mode) VALUES (?, ?, ?)`, step, at, fault.mode);
136
- sql.exec(`INSERT INTO harness_publish_armed (step, at, armed) VALUES (?, ?, ?)`, step, at, armedCompensations(state));
137
126
  // Everything written so far — including a step's own effect, for an `after` fault — is made
138
127
  // durable first, exactly as a real crash after the write would leave it.
139
128
  await state.storage.sync();
@@ -182,11 +171,104 @@ export function publishWorldOf(state: DurableObjectState): { kv: TestKv; world:
182
171
  return { kv, world: new PublishWorld(kv) };
183
172
  }
184
173
 
185
- /** Register the publish kinds (fault-wrapped, over the stand-in world) into `registry`; `pointers` is the object's authority. */
186
- export function registerHarnessPublishKinds(state: DurableObjectState, registry: JobKindRegistry, pointers: PointerWriter): JobKindRegistry {
187
- const { kv, world } = publishWorldOf(state);
188
- const store = testVersionStore(kv, pointers);
189
- const environment = standInPublishEnvironment({ kv, world, store, now: () => Date.now(), timing: PUBLISH_HARNESS_TIMING });
174
+ /** The world's logs that record an external effect, by the effect name the fault matrix uses. */
175
+ const NOTED_EFFECTS: Record<string, string> = {
176
+ "hotfix-merges": "forge:merge-to-main",
177
+ "forward-integrations": "forge:forward-integrate",
178
+ "staged-originals": "r2:stage-originals",
179
+ "went-live": "notice:went-live",
180
+ };
181
+
182
+ /** The publish world, reporting every external effect a job makes to the fault matrix. */
183
+ class EffectWorld extends PublishWorld {
184
+ constructor(
185
+ kv: TestKv,
186
+ private readonly landed: Landed,
187
+ ) {
188
+ super(kv);
189
+ }
190
+
191
+ override async dispatch(dispatch: CiDispatch, at = 0): Promise<void> {
192
+ const before = (await this.dispatches()).length;
193
+ let lost: unknown = null;
194
+ try {
195
+ await super.dispatch(dispatch, at);
196
+ } catch (thrown) {
197
+ // A refused dispatch recorded nothing (no run exists); a lost response came after the run did.
198
+ if ((await this.dispatches()).length === before) throw thrown;
199
+ lost = thrown;
200
+ }
201
+ await this.landed(`ci:${dispatch.workflow}`);
202
+ if (lost) throw lost;
203
+ }
204
+
205
+ override async recordDesignation(entry: WorldDesignation): Promise<void> {
206
+ await super.recordDesignation(entry);
207
+ await this.landed("mcp:designate");
208
+ }
209
+
210
+ override async note(key: string, value: unknown): Promise<void> {
211
+ await super.note(key, value);
212
+ const effect = NOTED_EFFECTS[key];
213
+ if (effect) await this.landed(effect);
214
+ }
215
+ }
216
+
217
+ /** The object's pointer writer, reporting each write that moved a channel (`pointer:<channel>`). */
218
+ function effectPointers(inner: PointerWriter, landed: Landed): PointerWriter {
219
+ return {
220
+ read: (tenantId) => inner.read(tenantId),
221
+ ...(inner.peek ? { peek: (tenantId: string) => inner.peek!(tenantId) } : {}),
222
+ async update(tenantId, apply) {
223
+ let before: PointerRecord | null = null;
224
+ const after = await inner.update(tenantId, (current) => {
225
+ before = current;
226
+ return apply(current);
227
+ });
228
+ const prior = before as PointerRecord | null;
229
+ for (const channel of ["live", "preview"] as const) {
230
+ if (prior?.channels?.[channel] !== after.channels?.[channel] || prior?.channelRevs?.[channel] !== after.channelRevs?.[channel]) {
231
+ await landed(`pointer:${channel}`);
232
+ }
233
+ }
234
+ return after;
235
+ },
236
+ };
237
+ }
238
+
239
+ /** The job's KV, reporting each write of the assets pointer (`assets:live`). */
240
+ function effectKv(kv: TestKv, landed: Landed): TestKv {
241
+ return {
242
+ get: (key) => kv.get(key),
243
+ put: async (key, value) => {
244
+ await kv.put(key, value);
245
+ if (key.startsWith("cust:assets-pointer:")) await landed("assets:live");
246
+ },
247
+ ...(kv.delete ? { delete: (key: string) => kv.delete!(key) } : {}),
248
+ };
249
+ }
250
+
251
+ const noEffects: Landed = async () => {};
252
+
253
+ /**
254
+ * Register the publish kinds (fault-wrapped, over the stand-in world) into `registry`; `pointers` is
255
+ * the object's authority. `landed` hears every external effect the jobs make (the fault matrix).
256
+ */
257
+ export function registerHarnessPublishKinds(
258
+ state: DurableObjectState,
259
+ registry: JobKindRegistry,
260
+ pointers: PointerWriter,
261
+ landed: Landed = noEffects,
262
+ ): JobKindRegistry {
263
+ const { kv } = publishWorldOf(state);
264
+ const store = testVersionStore(kv, effectPointers(pointers, landed));
265
+ const environment = standInPublishEnvironment({
266
+ kv: effectKv(kv, landed),
267
+ world: new EffectWorld(kv, landed),
268
+ store,
269
+ now: () => Date.now(),
270
+ timing: PUBLISH_HARNESS_TIMING,
271
+ });
190
272
  const kinds = registerPublishJobKinds(new JobKindRegistry(), async () => environment, PUBLISH_HARNESS_TUNING);
191
273
  for (const name of kinds.list()) registry.register(withFaults(state, kinds.get(name)!, store));
192
274
  return registry;