@tokenoftrust/storefront-runner 2.4.6 → 2.4.8

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.
@@ -4,21 +4,30 @@
4
4
  * restarts the object without touching its callers). It runs the production engine with the
5
5
  * synthetic job kinds, the production preview kinds (integrate / rebuild / revert / sync-preview) over
6
6
  * fake services (`./fake-preview-services.ts`) and the real publish kinds over the stand-in world
7
- * (`./publish-kinds.ts`), and answers intents through `submitIntent`.
7
+ * (`./publish-kinds.ts`), and answers intents through `submitIntent`. Every kind is wrapped with the
8
+ * fault matrix's kill points (`./matrix-faults.ts`), inert until a test arms one.
8
9
  */
9
10
  import type { DurableObjectState } from "cloudflare:workers";
10
11
  import { TenantPipeline as DeployedTenantPipeline } from "../../src/lib/pipeline/tenant-pipeline";
11
12
  import { submitIntent, tenantPipeline, type TenantPipelineNamespace } from "../../src/lib/pipeline/intake";
12
13
  import { submitPreviewSync } from "../../src/lib/pipeline/preview-sync-intake";
13
- import { StaleExecutionError, type JobKindRegistry, type PipelineIntent } from "../../src/lib/pipeline/job-runner";
14
+ import { JobKindRegistry, StaleExecutionError, type PipelineIntent } from "../../src/lib/pipeline/job-runner";
14
15
  import { abandonedContexts, syntheticJobKinds, syntheticSpans } from "./synthetic-job";
15
16
  import { registerPreviewJobKinds, type PreviewJobTiming } from "../../src/lib/pipeline/preview-jobs";
16
17
  import { configureFake, fakePreviewServices, readFakeState, type FakeFault, type FakeState } from "./fake-preview-services";
18
+ import { MatrixFaults, previewServicesWithEffects, type Landed, type MatrixFault } from "./matrix-faults";
19
+ import { kvAbandonServices, legacyOperationKey, registerSeedJobKind, type SeedServices } from "../../src/lib/pipeline/seed-from-legacy";
20
+ import { readAssetsPointer } from "@tot/public-runtime";
17
21
  import { readLivePointer } from "../../src/lib/pipeline/publish/live-pointer";
18
- import { listReleaseReceipts, readReleaseTruth } from "../../src/lib/change/store/aggregate-release-store";
22
+ import {
23
+ listIntegrationRuns,
24
+ listReleaseReceipts,
25
+ readReleaseTruth,
26
+ writeIntegrationRun,
27
+ type ReleaseModelKv,
28
+ } from "../../src/lib/change/store/aggregate-release-store";
19
29
  import { projectionStore, seedGreenAggregate, testVersionStore, type TestKv } from "../../src/lib/pipeline/publish/publish-test-support";
20
30
  import {
21
- armedAtFaults,
22
31
  armPublishFault,
23
32
  firedPublishFaults,
24
33
  publishWorldOf,
@@ -27,6 +36,9 @@ import {
27
36
  } from "./publish-kinds";
28
37
 
29
38
  /** The preview kinds' timing in the harness: the production shape, scaled to milliseconds. */
39
+ /** The seed kind's lease and retries in the harness. */
40
+ const HARNESS_SEED_TIMING = { leaseMs: 1_000, deadlineMs: 30_000, retryBaseMs: 10 };
41
+
30
42
  export const HARNESS_PREVIEW_TIMING: PreviewJobTiming = {
31
43
  leaseMs: 1_000,
32
44
  deadlineMs: 30_000,
@@ -35,13 +47,133 @@ export const HARNESS_PREVIEW_TIMING: PreviewJobTiming = {
35
47
  defaultExpectedMs: 2_000,
36
48
  };
37
49
 
50
+ /**
51
+ * The seed's services in the harness: what is served, read from this object's pointer authority (as
52
+ * the deployed object reads it), and the close-out of legacy records over the harness KV — each
53
+ * close-out reported to the fault matrix.
54
+ */
55
+ function harnessSeedServices(
56
+ ctx: DurableObjectState,
57
+ pointers: { read(tenantId: string): Promise<{ channels?: { preview?: string; live?: string } }> },
58
+ landed: Landed,
59
+ ): SeedServices {
60
+ const abandon = kvAbandonServices(publishWorldOf(ctx).kv as unknown as ReleaseModelKv);
61
+ return {
62
+ async pointers(tenantId) {
63
+ const pointer = await pointers.read(tenantId);
64
+ return { preview: pointer.channels?.preview ?? null, live: pointer.channels?.live ?? null };
65
+ },
66
+ async abandonRun(tenantId, runId, at) {
67
+ const changed = await abandon.abandonRun(tenantId, runId, at);
68
+ await landed("kv:abandon-run");
69
+ return changed;
70
+ },
71
+ async abandonOp(tenantId, op, at) {
72
+ const changed = await abandon.abandonOp(tenantId, op, at);
73
+ await landed("kv:abandon-op");
74
+ return changed;
75
+ },
76
+ };
77
+ }
78
+
79
+ /** Per object: the fault matrix's injection points and the (wrapped) registry it runs. */
80
+ const matrices = new WeakMap<DurableObjectState, { faults: MatrixFaults; registry: JobKindRegistry }>();
81
+
38
82
  export class TenantPipeline extends DeployedTenantPipeline {
39
83
  protected override jobKinds(ctx: DurableObjectState): JobKindRegistry {
40
- return registerHarnessPublishKinds(
84
+ const faults = new MatrixFaults(ctx, () => publishWorldOf(ctx).kv);
85
+ const services = previewServicesWithEffects(fakePreviewServices(ctx, faults.landed), faults.landed);
86
+ const registry = registerHarnessPublishKinds(
41
87
  ctx,
42
- registerPreviewJobKinds(syntheticJobKinds(ctx), fakePreviewServices(ctx), HARNESS_PREVIEW_TIMING),
88
+ registerPreviewJobKinds(syntheticJobKinds(ctx), services, HARNESS_PREVIEW_TIMING),
43
89
  this.pointers,
90
+ faults.landed,
44
91
  );
92
+ // The production seed kind, its lease and retries scaled to the harness (an evicted seed resumes
93
+ // when its lease runs out, as in production).
94
+ const seed = registerSeedJobKind(new JobKindRegistry(), harnessSeedServices(ctx, this.pointers, faults.landed));
95
+ for (const name of seed.list()) registry.register({ ...seed.get(name)!, ...HARNESS_SEED_TIMING });
96
+ const wrapped = faults.wrap(registry);
97
+ matrices.set(ctx, { faults, registry: wrapped });
98
+ return wrapped;
99
+ }
100
+
101
+ /** Harness-only: arm and read the fault matrix (`./matrix-faults.ts`, `test/pipeline-faults/`). */
102
+ async matrix(op: string, args: Record<string, unknown> = {}): Promise<unknown> {
103
+ const { faults, registry } = matrices.get(this.ctx)!;
104
+ switch (op) {
105
+ case "arm":
106
+ faults.arm(args as unknown as MatrixFault);
107
+ return null;
108
+ case "held":
109
+ return faults.held();
110
+ case "release":
111
+ faults.release();
112
+ return null;
113
+ case "kills":
114
+ return faults.kills();
115
+ case "effects":
116
+ return faults.effects(args.jobId as string | undefined);
117
+ case "execs":
118
+ return faults.execs(String(args.jobId));
119
+ case "health":
120
+ return faults.health();
121
+ case "kinds":
122
+ return registry.list().map((kind) => {
123
+ const definition = registry.get(kind)!;
124
+ return { kind, lane: definition.lane ?? "live", steps: definition.steps.map((s) => s.name) };
125
+ });
126
+ case "endState":
127
+ return this.endState(String(args.tenantId));
128
+ default:
129
+ throw new Error(`unknown matrix op ${op}`);
130
+ }
131
+ }
132
+
133
+ /** What a converged job leaves behind, on both lanes — the fault matrix compares it to an unfaulted run. */
134
+ private async endState(tenantId: string): Promise<Record<string, unknown>> {
135
+ const { kv, world } = publishWorldOf(this.ctx);
136
+ const truth = await readReleaseTruth(kv, tenantId, "preview");
137
+ const fake = readFakeState(this.ctx);
138
+ return {
139
+ live: await readLivePointer(projectionStore(kv), tenantId),
140
+ liveRecords: this.ctx.storage.sql
141
+ .exec<{ provider: string; sha: string; receipt_id: string }>(`SELECT provider, sha, receipt_id FROM live ORDER BY provider`)
142
+ .toArray()
143
+ .map((r) => ({ provider: r.provider, sha: r.sha, receiptId: r.receipt_id })),
144
+ truth: truth
145
+ ? {
146
+ liveReceiptId: truth.liveReceiptId ?? null,
147
+ pendingReceiptId: truth.pendingReceiptId ?? null,
148
+ deliveryState: truth.deliveryState ?? null,
149
+ unprovenLive: truth.unprovenLive ?? null,
150
+ liveDisagrees: truth.liveDisagrees ? { serving: truth.liveDisagrees.serving, recordedReceiptId: truth.liveDisagrees.recordedReceiptId } : null,
151
+ }
152
+ : null,
153
+ receipts: (await listReleaseReceipts(kv, tenantId, "preview")).map((r) => ({ receiptId: r.receiptId, sha: r.aggregateSha })),
154
+ promotion: await world.promotionStatus(),
155
+ cloudfront: await world.cloudFrontServes(),
156
+ assetsLive: (await readAssetsPointer({ get: (k: string) => kv.get(k) }, tenantId)).live ?? null,
157
+ legacy: {
158
+ runs: (await listIntegrationRuns(kv as unknown as ReleaseModelKv, tenantId, "preview")).map((r) => ({ runId: r.runId, state: r.state })),
159
+ op: await (async () => {
160
+ const raw = await kv.get(legacyOperationKey(tenantId, { operation: "publication", subjectId: "op-stale" }));
161
+ return raw ? ((JSON.parse(raw) as { problem?: { code?: string } | null }).problem?.code ?? "open") : null;
162
+ })(),
163
+ },
164
+ preview: {
165
+ pointer: fake.previewPointer,
166
+ readModel: fake.readModel,
167
+ merges: fake.merges.map((m) => ({ changeId: m.changeId, sha: m.sha })),
168
+ commits: this.ctx.storage.sql
169
+ .exec<{ sha: string; membership: string }>(`SELECT sha, membership FROM commits ORDER BY sha`)
170
+ .toArray()
171
+ .map((r) => ({ sha: r.sha, membership: JSON.parse(r.membership) as unknown })),
172
+ evidence: this.ctx.storage.sql
173
+ .exec<{ sha: string; digest: string }>(`SELECT sha, digest FROM evidence ORDER BY sha`)
174
+ .toArray(),
175
+ },
176
+ };
45
177
  }
46
178
 
47
179
  /**
@@ -123,13 +255,46 @@ export class TenantPipeline extends DeployedTenantPipeline {
123
255
  Number(args.times ?? 1),
124
256
  );
125
257
  return null;
258
+ case "seedPointers":
259
+ // What the shared preview and the live site are served from, as a legacy store left them.
260
+ await this.pointers.update(tenantId, (current) => ({
261
+ ...current,
262
+ channels: { ...current.channels, preview: String(args.preview), live: String(args.live) },
263
+ channelRevs: { ...current.channelRevs, preview: (current.channelRevs?.preview ?? 0) + 1, live: (current.channelRevs?.live ?? 0) + 1 },
264
+ }));
265
+ return null;
266
+ case "seedLegacy": {
267
+ // A request-scoped run and an operation that never finished.
268
+ const at = "2026-10-09T00:00:00.000Z";
269
+ await writeIntegrationRun(
270
+ kv as unknown as ReleaseModelKv,
271
+ {
272
+ tenantId,
273
+ aggregateId: "preview",
274
+ runId: String(args.runId),
275
+ aggregateSha: String(args.sha),
276
+ includedPrs: [],
277
+ state: "running",
278
+ previousRunId: null,
279
+ artifactDigest: null,
280
+ startedAt: at,
281
+ finishedAt: null,
282
+ statusMessage: null,
283
+ },
284
+ 0,
285
+ );
286
+ const op = { operation: "publication" as const, subjectId: String(args.opSubject) };
287
+ await kv.put(
288
+ legacyOperationKey(tenantId, op),
289
+ JSON.stringify({ tenantId, ...op, revision: String(args.sha), stage: "publishing", problem: null, attemptsMade: 1, updatedAt: at, rev: 0 }),
290
+ );
291
+ return null;
292
+ }
126
293
  case "armFault":
127
294
  armPublishFault(this.ctx, args as unknown as PublishFault);
128
295
  return null;
129
296
  case "firedFaults":
130
297
  return firedPublishFaults(this.ctx);
131
- case "armedAtFaults":
132
- return armedAtFaults(this.ctx);
133
298
  default:
134
299
  throw new Error(`unknown publish harness op ${op}`);
135
300
  }
@@ -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,26 @@ import {
26
26
  movePreview,
27
27
  standInPublishEnvironment,
28
28
  testVersionStore,
29
+ type CiDispatch,
29
30
  type TestKv,
30
31
  } from "../../src/lib/pipeline/publish/publish-test-support";
32
+ import type { Landed } from "./matrix-faults";
31
33
 
32
34
  type SiteVersionStore = ReturnType<typeof testVersionStore>;
33
35
  type PointerWriter = NonNullable<Parameters<typeof testVersionStore>[1]>;
36
+ type PointerRecord = Awaited<ReturnType<PointerWriter["read"]>>;
34
37
 
38
+ /**
39
+ * Production's ratios, in seconds: a CI window (45 min) outlasts the lease an evicted object waits
40
+ * out before it resumes (10 min, here `leaseMs`), so a job resumed after an eviction is still inside
41
+ * its window — as in production.
42
+ */
35
43
  export const PUBLISH_HARNESS_TIMING: PublishTiming = {
36
- awaitWindowMs: 1_500,
44
+ awaitWindowMs: 4_000,
37
45
  runGraceMs: 500,
38
46
  proofDelayMs: 30,
39
47
  proofProbes: { synchronous: 3, asynchronous: 5 },
40
- stageWindowMs: 1_500,
48
+ stageWindowMs: 4_000,
41
49
  stageChecks: 3,
42
50
  };
43
51
 
@@ -64,25 +72,6 @@ function tables(state: DurableObjectState): void {
64
72
  sql.exec(`CREATE TABLE IF NOT EXISTS harness_kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)`);
65
73
  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
74
  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
75
  }
87
76
 
88
77
  /** A KV over the object's SQLite: durable across evictions, visible to every later execution. */
@@ -133,7 +122,6 @@ async function maybeFault(state: DurableObjectState, step: string, at: "before"
133
122
  if (!fault) return;
134
123
  sql.exec(`UPDATE harness_publish_faults SET remaining = remaining - 1 WHERE id = ?`, fault.id);
135
124
  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
125
  // Everything written so far — including a step's own effect, for an `after` fault — is made
138
126
  // durable first, exactly as a real crash after the write would leave it.
139
127
  await state.storage.sync();
@@ -182,11 +170,104 @@ export function publishWorldOf(state: DurableObjectState): { kv: TestKv; world:
182
170
  return { kv, world: new PublishWorld(kv) };
183
171
  }
184
172
 
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 });
173
+ /** The world's logs that record an external effect, by the effect name the fault matrix uses. */
174
+ const NOTED_EFFECTS: Record<string, string> = {
175
+ "hotfix-merges": "forge:merge-to-main",
176
+ "forward-integrations": "forge:forward-integrate",
177
+ "staged-originals": "r2:stage-originals",
178
+ "went-live": "notice:went-live",
179
+ };
180
+
181
+ /** The publish world, reporting every external effect a job makes to the fault matrix. */
182
+ class EffectWorld extends PublishWorld {
183
+ constructor(
184
+ kv: TestKv,
185
+ private readonly landed: Landed,
186
+ ) {
187
+ super(kv);
188
+ }
189
+
190
+ override async dispatch(dispatch: CiDispatch, at = 0): Promise<void> {
191
+ const before = (await this.dispatches()).length;
192
+ let lost: unknown = null;
193
+ try {
194
+ await super.dispatch(dispatch, at);
195
+ } catch (thrown) {
196
+ // A refused dispatch recorded nothing (no run exists); a lost response came after the run did.
197
+ if ((await this.dispatches()).length === before) throw thrown;
198
+ lost = thrown;
199
+ }
200
+ await this.landed(`ci:${dispatch.workflow}`);
201
+ if (lost) throw lost;
202
+ }
203
+
204
+ override async recordDesignation(entry: { target: string; versionId: string; digest: string }): Promise<void> {
205
+ await super.recordDesignation(entry);
206
+ await this.landed("mcp:designate");
207
+ }
208
+
209
+ override async note(key: string, value: unknown): Promise<void> {
210
+ await super.note(key, value);
211
+ const effect = NOTED_EFFECTS[key];
212
+ if (effect) await this.landed(effect);
213
+ }
214
+ }
215
+
216
+ /** The object's pointer writer, reporting each write that moved a channel (`pointer:<channel>`). */
217
+ function effectPointers(inner: PointerWriter, landed: Landed): PointerWriter {
218
+ return {
219
+ read: (tenantId) => inner.read(tenantId),
220
+ ...(inner.peek ? { peek: (tenantId: string) => inner.peek!(tenantId) } : {}),
221
+ async update(tenantId, apply) {
222
+ let before: PointerRecord | null = null;
223
+ const after = await inner.update(tenantId, (current) => {
224
+ before = current;
225
+ return apply(current);
226
+ });
227
+ const prior = before as PointerRecord | null;
228
+ for (const channel of ["live", "preview"] as const) {
229
+ if (prior?.channels?.[channel] !== after.channels?.[channel] || prior?.channelRevs?.[channel] !== after.channelRevs?.[channel]) {
230
+ await landed(`pointer:${channel}`);
231
+ }
232
+ }
233
+ return after;
234
+ },
235
+ };
236
+ }
237
+
238
+ /** The job's KV, reporting each write of the assets pointer (`assets:live`). */
239
+ function effectKv(kv: TestKv, landed: Landed): TestKv {
240
+ return {
241
+ get: (key) => kv.get(key),
242
+ put: async (key, value) => {
243
+ await kv.put(key, value);
244
+ if (key.startsWith("cust:assets-pointer:")) await landed("assets:live");
245
+ },
246
+ ...(kv.delete ? { delete: (key: string) => kv.delete!(key) } : {}),
247
+ };
248
+ }
249
+
250
+ const noEffects: Landed = async () => {};
251
+
252
+ /**
253
+ * Register the publish kinds (fault-wrapped, over the stand-in world) into `registry`; `pointers` is
254
+ * the object's authority. `landed` hears every external effect the jobs make (the fault matrix).
255
+ */
256
+ export function registerHarnessPublishKinds(
257
+ state: DurableObjectState,
258
+ registry: JobKindRegistry,
259
+ pointers: PointerWriter,
260
+ landed: Landed = noEffects,
261
+ ): JobKindRegistry {
262
+ const { kv } = publishWorldOf(state);
263
+ const store = testVersionStore(kv, effectPointers(pointers, landed));
264
+ const environment = standInPublishEnvironment({
265
+ kv: effectKv(kv, landed),
266
+ world: new EffectWorld(kv, landed),
267
+ store,
268
+ now: () => Date.now(),
269
+ timing: PUBLISH_HARNESS_TIMING,
270
+ });
190
271
  const kinds = registerPublishJobKinds(new JobKindRegistry(), async () => environment, PUBLISH_HARNESS_TUNING);
191
272
  for (const name of kinds.list()) registry.register(withFaults(state, kinds.get(name)!, store));
192
273
  return registry;