cursedops 0.18.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ import shape, worked example, and the incident each one is pinned to.
25
25
  | `cursedops/backups` | `BACKUP_KEEP`, the fleet's one retention for dated snapshots; `BACKUP_STAMP_PATTERN`, `backupStamp` and `stampTime` |
26
26
  | `cursedops/serve` | the static tier's four helpers — the path-traversal guard, the MIME table, the hashed-asset test, the crash handlers |
27
27
  | `cursedops/worker-safety` | the two static checks every Worker app's suite runs over its own source — the import-graph walk an app's own test does from `worker/index.ts` (a value import of `bun:sqlite` or a Bun-only subpath is a deploy that goes green and a Worker that never starts), and the await port's silent shapes (`rows.filter(async …)` keeps every row) |
28
- | `cursedops/api-floor` | the rule that an unmatched `/api/...` is a phrase and never the app shell — the namespace predicates, the trailing-slash normaliser, the default 404 body |
28
+ | `cursedops/api-floor` | the rule that an unmatched `/api/...` is a phrase and never the app shell — the namespace predicates, the trailing-slash normalizer, the default 404 body |
29
29
  | `cursedops/build-info` | which commit a checkout-served process is running and whether its tree was dirty, read once at load. `node:child_process`, so never in a Worker |
30
30
  | `cursedops/bound-lists` | the static check that no SQL builds an `IN (…)` list one `?` per value — the shape D1 500s on past 100 ids |
31
31
  | `cursedops/staged-client` | the IMMUTABLE client a checkout-served app serves — a build staged into `<APP_DATA_DIR>/client/<commit>/` behind `CURRENT` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedops",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "description": "The build-and-ops answers this generation's apps wrote independently and identically: finding a generation's roots — and printing a command that runs when pasted — without knowing a path, the generation's whole-tree laws run over one repo from a checkout or a worktree, macOS launchd agent install/replace/remove and the live port a job serves, the scaffolding and verdicts of a deployed smoke (origin probe, the smoke's own environment, a network that lies about DNS, a settled version), the static-serving helpers eight apps copied — the path-traversal guard among them — the API floor that keeps an unmatched /api/... from ever being answered with the app shell, the commit and dirty flag a checkout-served process reports, the Cloudflare Worker deploy toolkit four apps copied (the deploy sequence, exact-set secrets over a pipe, origin-first rollback, the curl edge fetch, the row-for-row D1 import proof, the billed-CPU tail check around a deploy's walk and smoke, and each app's worker:secrets and worker:smoke main as one function of its data), the relay a Worker fronts a Mac-bound app with (the Durable Object, the frames, the Mac's dialer and key rotation — lifted from station for roms — and the signed-in stage walk's skeleton and the relay app's whole worker:deploy), the Worker import-graph and await-port checks every Worker app's suite runs over its own source, and the public-surface ratchet three published libraries each carried a forked copy of. Mechanism only — no app knows its name from here. Bun, zero runtime dependencies (typescript is an optional peer, for public-surface only), ships source.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/apiFloor.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * > this commit and reload"*, which sent the owner at a deploy that could not have
20
20
  * > helped.
21
21
  *
22
- * Both halves of that are here, because a floor without the normaliser fixes the symptom
22
+ * Both halves of that are here, because a floor without the normalizer fixes the symptom
23
23
  * the owner did not have: {@link canonicalApiPath} makes the trailing slash a non-event,
24
24
  * and {@link isApiPath} + {@link apiNotFoundBody} make every remaining miss a JSON 404
25
25
  * that names the route, the app and the commit answering. A wrong path says "wrong path";
@@ -73,7 +73,7 @@ export const isApiPath = (urlPath: string): boolean =>
73
73
  * 🔴 One rule, and it is the one that broke the Openclaw page: a trailing slash on an API
74
74
  * path is never meaningful. Hono mounts a sub-app's `get("/")` at the mount point WITHOUT
75
75
  * a trailing slash and then matches strictly, so `/api/openclaw/` is a different path
76
- * from `/api/openclaw` and matched nothing at all. Normalising once, in front of the
76
+ * from `/api/openclaw` and matched nothing at all. Normalizing once, in front of the
77
77
  * router, means no caller anywhere can spell it the losing way again — which a fix in the
78
78
  * one client module that happened to do it would not have bought.
79
79
  *
@@ -86,8 +86,8 @@ export function canonicalApiPath(urlPath: string): string | null {
86
86
  }
87
87
 
88
88
  /**
89
- * The same normalisation, applied to a whole request. Returns the request unchanged when
90
- * there is nothing to normalise, so a caller that guards on {@link canonicalApiPath}
89
+ * The same normalization, applied to a whole request. Returns the request unchanged when
90
+ * there is nothing to normalize, so a caller that guards on {@link canonicalApiPath}
91
91
  * allocates nothing.
92
92
  *
93
93
  * 🔴 **Call it from INSIDE the floor handler and re-dispatch, not from the host's
package/src/d1Import.ts CHANGED
@@ -14,17 +14,17 @@
14
14
  * ## 🔴 Row for row, never a count
15
15
  *
16
16
  * Two tables can hold the same NUMBER of different rows, and a count is exactly the check that
17
- * cannot tell — on `collections`' catalogue that would have been a caption, a rotation or a
17
+ * cannot tell — on `collections`' catalog that would have been a caption, a rotation or a
18
18
  * checksum silently replaced by another file's. `collections`, `vault`, `patterns` and `music`
19
19
  * each wrote this comparison (task 2097); the `rowDigest` bodies had drifted:
20
20
  *
21
- * · `collections` normalised numbers — `bun:sqlite` hands back `0` where D1's JSON carries
21
+ * · `collections` normalized numbers — `bun:sqlite` hands back `0` where D1's JSON carries
22
22
  * `0.0`, and float noise past twelve significant digits — because `duration_seconds` is REAL
23
- * on 39 of its rows and a comparison that called those different reported the catalogue
24
- * corrupt on every run. `vault` and `music` did not normalise, and would have.
23
+ * on 39 of its rows and a comparison that called those different reported the catalog
24
+ * corrupt on every run. `vault` and `music` did not normalize, and would have.
25
25
  * · `vault` and `music` spelled a BLOB as `X'…'` for the insert; `patterns` could not carry one.
26
26
  *
27
- * The body here is the union: numbers normalised, blobs compared as hex whichever side they came
27
+ * The body here is the union: numbers normalized, blobs compared as hex whichever side they came
28
28
  * from (a `Uint8Array` from `bun:sqlite`, an array of bytes from D1's JSON), booleans as 0/1.
29
29
  */
30
30
 
@@ -54,7 +54,7 @@ function cell(value: unknown): string | number | null {
54
54
  return String(value);
55
55
  }
56
56
 
57
- /** One row flattened to a comparable string over `columns` — see the header for the normalisation. */
57
+ /** One row flattened to a comparable string over `columns` — see the header for the normalization. */
58
58
  export function rowDigest(row: Readonly<Record<string, unknown>>, columns: readonly string[]): string {
59
59
  return JSON.stringify(columns.map((column) => cell(row[column])));
60
60
  }
package/src/edgeFetch.ts CHANGED
@@ -271,7 +271,7 @@ export const NO_ANSWER_STATUS = 599;
271
271
  * {@link NO_ANSWER_STATUS} `Response` whose body and `x-edge-error` header carry the reason, and
272
272
  * `onThrow` hears it first so the smoke's ledger can name it.
273
273
  *
274
- * 🔴 Why (2026-09-25): roms' `worker:smoke` asked a relayed path while the Mac's link redialled,
274
+ * 🔴 Why (2026-09-25): roms' `worker:smoke` asked a relayed path while the Mac's link redialed,
275
275
  * `edgeFetch` threw on curl exit 28, and the process died with a stack trace and no ledger — so a
276
276
  * healthy release exited 1. Every leg of a smoke is supposed to be RECORDED: a throw is a failed
277
277
  * leg naming its path, never the end of the run.
package/src/launchd.ts CHANGED
@@ -66,7 +66,7 @@ export interface CommandResult {
66
66
  /**
67
67
  * How this module reaches launchd and the LaunchAgents directory.
68
68
  *
69
- * Injectable as one object because the interesting behaviour — clearing a label
69
+ * Injectable as one object because the interesting behavior — clearing a label
70
70
  * and *waiting* for it to go — cannot otherwise be reached from a test that does
71
71
  * not install a real login agent first, and a test nobody runs is not a test.
72
72
  */
package/src/relay.ts CHANGED
@@ -38,7 +38,7 @@ export const DEFAULT_MAX_BODY_BYTES = 4 * 1024 * 1024;
38
38
  /** How long the Worker waits for the Mac to answer one request before it says so. */
39
39
  export const DEFAULT_RELAY_TIMEOUT_MS = 30_000;
40
40
 
41
- /** A request travelling DOWN the link. `path` includes the query string. */
41
+ /** A request traveling DOWN the link. `path` includes the query string. */
42
42
  export interface RequestFrame {
43
43
  t: "req";
44
44
  id: string;
@@ -48,7 +48,7 @@ export interface RequestFrame {
48
48
  body: string | null;
49
49
  }
50
50
 
51
- /** The Mac's answer, travelling UP. Multiple `set-cookie` lines stay separate pairs. */
51
+ /** The Mac's answer, traveling UP. Multiple `set-cookie` lines stay separate pairs. */
52
52
  export interface ResponseFrame {
53
53
  t: "res";
54
54
  id: string;
@@ -459,7 +459,7 @@ export class RelayLink {
459
459
  if (body.length <= (cache.maxKeptBody ?? 512 * 1024)) {
460
460
  const at = this.now();
461
461
  await this.state.storage.put(`c:${owner}:${keyed}`, { body, contentType: response.headers.get("content-type") ?? "application/json", at } satisfies Kept);
462
- // The newest cookie the Mac honoured is the one a `warm` frame refreshes under.
462
+ // The newest cookie the Mac honored is the one a `warm` frame refreshes under.
463
463
  await this.state.storage.put("warm", { cookie, at });
464
464
  }
465
465
  }
@@ -474,7 +474,7 @@ export class RelayLink {
474
474
  if (keys.length > 0) await this.state.storage.delete(keys);
475
475
  }
476
476
 
477
- /** Refresh every read the Mac named, under the newest cookie it honoured — if that is still fresh. */
477
+ /** Refresh every read the Mac named, under the newest cookie it honored — if that is still fresh. */
478
478
  private async warm(paths: readonly string[]): Promise<void> {
479
479
  const cache = this.options.offline;
480
480
  if (!cache) return;
package/src/relayStage.ts CHANGED
@@ -163,7 +163,7 @@ export const realRun: Run = (argv, options) => {
163
163
  /**
164
164
  * 🔴 How many rounds in a row every `/healthz` must name a just-put link digest before a walk uses it.
165
165
  * A digest put is a new Worker version, and the edge takes it up instance by instance: the link
166
- * staying up for 15 s proves only that the instance the link dialled has it. The Worker signs the
166
+ * staying up for 15 s proves only that the instance the link dialed has it. The Worker signs the
167
167
  * Mac's identity with that digest, so a request answered by an instance still on the old one is the
168
168
  * Mac's own 401 (and station's kept read a 503). Measured 2026-10-15 (forge task 3235): station's
169
169
  * fast-lane walk at baf8495, the link steady for 15 s, then four writes 401 at the Mac; on the real
package/src/roots.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * written independently against the same problem; measured 2026-09-16 the
9
9
  * `findForgeRoot` bodies were character-identical and the surrounding halves were
10
10
  * not, which is the definition of an extraction that is measurable rather than
11
- * speculative. Two implementations, one behaviour.
11
+ * speculative. Two implementations, one behavior.
12
12
  *
13
13
  * ## The rule this serves
14
14
  *
@@ -332,7 +332,7 @@ function shellDoubleQuoted(value: string): string {
332
332
  * {@link findForgeRoot}).
333
333
  *
334
334
  * 🔴 **Verified, never trusted.** A `$FORGE` pointing at a directory with no `forge.env` is a
335
- * stale export from a shell that outlived a move, and honouring it re-creates the exact defect
335
+ * stale export from a shell that outlived a move, and honoring it re-creates the exact defect
336
336
  * {@link repoCd} exists to end: a command naming a directory that is not there.
337
337
  * {@link forgeCodeRoot} deliberately keeps its older contract (the variable wins unchecked); this
338
338
  * is the one for text a person will paste.
package/src/serve.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * `station` and `vault` each have a `src/server/serve.ts`. The FILES differ — 247
10
10
  * to 387 lines, eight distinct versions, which is why no `diff` ever flagged them —
11
11
  * but these four bodies are character-identical in **eight of eight**, with the
12
- * whitespace and comments normalised away.
12
+ * whitespace and comments normalized away.
13
13
  *
14
14
  * 🔴 **`fileWithin` is why this was urgent rather than tidy.** It is the check that
15
15
  * stops `%2e%2e%2f` reaching the filesystem. Eight copies of a security guarantee
@@ -28,7 +28,7 @@
28
28
  * `8 <symbol> src/server/serve.ts` and is the census that will notice if a copy
29
29
  * comes back.
30
30
  *
31
- * ## 🔴 The one behaviour that is NOT a byte-for-byte carry
31
+ * ## 🔴 The one behavior that is NOT a byte-for-byte carry
32
32
  *
33
33
  * The eight copies resolved containment LEXICALLY — `resolve(join(root, …))` and a
34
34
  * `startsWith` — which is correct for `..` and for `%2e%2e%2f`, and blind to a
@@ -42,7 +42,7 @@
42
42
  * the same way — the failure mode a naive `realpath` on one side only would
43
43
  * have introduced, and the reason this is not one line;
44
44
  * - every caller today passes a Vite `dist/`, and all eight were checked for
45
- * symlinks on 2026-09-17: there are none. So no app's behaviour moves, and the
45
+ * symlinks on 2026-09-17: there are none. So no app's behavior moves, and the
46
46
  * day one of them serves a directory it did not build, the guard already holds.
47
47
  *
48
48
  * `serve.test.ts` drives both halves, including the symlink escape, because a guard
package/src/ship.ts CHANGED
@@ -39,12 +39,12 @@
39
39
  * flipped yesterday — the precedence the retired `fastMode.ts` settled on 2026-08-13). A stage
40
40
  * deploy is never deferred: it IS the fast way to see a change on a real edge.
41
41
  *
42
- * 🔴 **Under a batch (`$AUTOPILOT_BATCH` set) it is honoured only for the ship task or an incident**
42
+ * 🔴 **Under a batch (`$AUTOPILOT_BATCH` set) it is honored only for the ship task or an incident**
43
43
  * ({@link shipNowVerdict}), and only with a reason in `FORGE_SHIP_NOW_WHY`. Until task 2384 any agent
44
44
  * could prefix one variable and turn the owner's switch off for itself — the thing
45
45
  * `~/.claude/CLAUDE.md` §2 says an agent may never do — and no record said it had. Now every ask,
46
- * honoured or refused, is a `kind: "ship-now"` ledger row, and `autopilot doctor` lists the ones
47
- * with no reason. In the owner's own shell (no batch) it is honoured exactly as before, and recorded.
46
+ * honored or refused, is a `kind: "ship-now"` ledger row, and `autopilot doctor` lists the ones
47
+ * with no reason. In the owner's own shell (no batch) it is honored exactly as before, and recorded.
48
48
  */
49
49
  import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
50
50
  import { dirname, join } from "node:path";
@@ -92,7 +92,7 @@ export function readShipSetting(state: string | null): ShipSetting {
92
92
  }
93
93
  }
94
94
 
95
- /** Is `FORGE_SHIP_NOW` set to something that means yes? Asked is not honoured — see {@link shipNowVerdict}. */
95
+ /** Is `FORGE_SHIP_NOW` set to something that means yes? Asked is not honored — see {@link shipNowVerdict}. */
96
96
  export function shipNowAsked(env: NodeJS.ProcessEnv = process.env): boolean {
97
97
  const v = env[SHIP_NOW_VAR]?.trim().toLowerCase();
98
98
  return v === "1" || v === "true" || v === "yes";
@@ -116,7 +116,7 @@ export interface ShipNowVerdict {
116
116
  asked: boolean;
117
117
  /** Ship now: asked, and (outside a batch, or on a claim that earns it). */
118
118
  honoured: boolean;
119
- /** One sentence: what made it honoured or refused. */
119
+ /** One sentence: what made it honored or refused. */
120
120
  why: string;
121
121
  /** `FORGE_SHIP_NOW_WHY`, trimmed — `""` when not given. */
122
122
  reason: string;
@@ -159,7 +159,7 @@ export interface ShipNowWhere {
159
159
  }
160
160
 
161
161
  /**
162
- * Is `FORGE_SHIP_NOW` honoured here? Outside a batch, whenever it is asked: that is the owner's own
162
+ * Is `FORGE_SHIP_NOW` honored here? Outside a batch, whenever it is asked: that is the owner's own
163
163
  * shell. Under `$AUTOPILOT_BATCH`, only when that batch holds a live `kind: "task"` board claim
164
164
  * (`$FORGE_STATE/state/board/*.json`, the pid alive and not expired) on a task in
165
165
  * `$FORGE/tasks/In_Progress/` whose `escalation-key:` is one of {@link SHIP_NOW_KEYS}, AND
@@ -206,11 +206,11 @@ export function shipNowVerdict(where: ShipNowWhere, env: NodeJS.ProcessEnv = pro
206
206
  if (key && SHIP_NOW_KEYS.some((k) => k.test(key))) return { ...base, honoured: true, why: `batch ${batch} holds ${c.task} (escalation-key: ${key})`, task: c.task };
207
207
  }
208
208
  return refuse(
209
- `batch ${batch} holds ${held.length ? held.join(", ") : "no live task claim"} — ${SHIP_NOW_VAR} is honoured under a batch only for the ship task or a prod incident (escalation-key: ship | prod-down:<host>)`,
209
+ `batch ${batch} holds ${held.length ? held.join(", ") : "no live task claim"} — ${SHIP_NOW_VAR} is honored under a batch only for the ship task or a prod incident (escalation-key: ship | prod-down:<host>)`,
210
210
  );
211
211
  }
212
212
 
213
- /** The ledger line every ship-now ask leaves — honoured or refused. */
213
+ /** The ledger line every ship-now ask leaves — honored or refused. */
214
214
  export interface ShipNowEventLine {
215
215
  at: string;
216
216
  kind: "ship-now";
package/src/smoke.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * its logs was wrong.
14
14
  * 3. A deploy *succeeded* while every byte of media was unreachable, because the
15
15
  * failing layer answered before the thing being health-checked ran.
16
- * 4. An app was up, green, and serving a catalogue a MONTH out of date.
16
+ * 4. An app was up, green, and serving a catalog a MONTH out of date.
17
17
  * 5. Two apps went live answering `no built client at …/public` at `/` while the
18
18
  * edge returned 200 out of its cache of the RETIRED deployment. Gate green,
19
19
  * deploy green, smoke green, app down; the owner found it, not a check.
@@ -62,7 +62,7 @@
62
62
  * built, `patterns.cursedalchemy.com` served one built on 2026-09-12 out of a
63
63
  * Worker nobody knew about, and **every automated surface was green for six days.**
64
64
  * The app-local implementation that found it — `apps/patterns/scripts/worker-smoke.ts`,
65
- * `CROSS_CHECKS` — is what this generalises, because five more apps are moving
65
+ * `CROSS_CHECKS` — is what this generalizes, because five more apps are moving
66
66
  * behind an edge Worker and a check told ONE NAME is a check about that name.
67
67
  *
68
68
  * ## What this module will never grow
@@ -501,7 +501,7 @@ export async function compareDeployedShells(addresses: string[], options: ShellO
501
501
  url.searchParams.set("cb", cacheBust());
502
502
  // 🔴 Re-thrown WITH the address and with the original message intact: which
503
503
  // of the addresses could not be reached is the whole diagnosis, and
504
- // {@link Smoke.dnsHint} still has to recognise an ENOTFOUND inside it.
504
+ // {@link Smoke.dnsHint} still has to recognize an ENOTFOUND inside it.
505
505
  const response = await send(url.toString(), {
506
506
  redirect: "manual",
507
507
  signal: AbortSignal.timeout(timeoutMs),
@@ -929,7 +929,7 @@ export async function steadyHealth<T = Record<string, unknown>>(
929
929
  */
930
930
  export const HEALTH_READ_MAX_TIME_SEC = 10;
931
931
 
932
- /** The init a smoke's `/healthz` read carries — `cursedops/edge-fetch` honours it; a plain `fetch` ignores it. */
932
+ /** The init a smoke's `/healthz` read carries — `cursedops/edge-fetch` honors it; a plain `fetch` ignores it. */
933
933
  const healthRead: EdgeRequestInit = { maxTimeSec: HEALTH_READ_MAX_TIME_SEC };
934
934
 
935
935
  /** One question a deploy must answer the NEW way — `null` when it does, else why not. */
@@ -1030,8 +1030,13 @@ export interface WorkerSmokeSpec {
1030
1030
  deployment: { env: string; workerName: string; publicUrl: string; previewUrl: string | null };
1031
1031
  /** Addresses given on the command line. Empty: the preview (no cache in front) and the public URL. */
1032
1032
  urls?: readonly string[];
1033
- /** The checkout's HEAD, as `/healthz` reports a commit (`git rev-parse --short=8 HEAD`). */
1033
+ /**
1034
+ * The checkout's HEAD, as `/healthz` reports a commit (`git rev-parse --short=8 HEAD`). Under
1035
+ * `runWorkerDeploy` the commit it STAMPED wins — see {@link DEPLOYED_COMMIT_ENV}.
1036
+ */
1034
1037
  head: string;
1038
+ /** Where {@link DEPLOYED_COMMIT_ENV} is read. Default `process.env`. */
1039
+ env?: Record<string, string | undefined>;
1035
1040
  /**
1036
1041
  * Every private route; each must refuse an anonymous caller with 401 (`door: "access"`: 403 on
1037
1042
  * the preview, 200 through the token on the public host). Empty is a FAILURE.
@@ -1059,6 +1064,25 @@ export interface WorkerSmokeSpec {
1059
1064
  log?: (line: string) => void;
1060
1065
  }
1061
1066
 
1067
+ /**
1068
+ * 🔴 The commit `runWorkerDeploy` stamped into the Worker it just uploaded, handed to its smoke.
1069
+ *
1070
+ * Each app's smoke reads its checkout's HEAD when it starts, minutes after the deploy read it to
1071
+ * stamp the build. On 2026-10-18 music's fast lane built and stamped `a0bca487`, a batch landed an
1072
+ * e2e-only commit two minutes in, and the smoke — asking for the new HEAD `43c2bae8` — waited out
1073
+ * its steady window and then failed a healthy production deploy on `commit`. The smoke proves
1074
+ * what was DEPLOYED, so it asks for this commit when it is set, and for HEAD only when run alone.
1075
+ */
1076
+ export const DEPLOYED_COMMIT_ENV = "CURSEDOPS_DEPLOYED_COMMIT";
1077
+
1078
+ /** The commit `/healthz` must report, and the words the `commit` check says it in. */
1079
+ export function expectedCommit(spec: Pick<WorkerSmokeSpec, "head" | "env">): { commit: string; said: string } {
1080
+ const stamped = (spec.env ?? process.env)[DEPLOYED_COMMIT_ENV]?.trim();
1081
+ if (!stamped) return { commit: spec.head, said: `HEAD ${spec.head}` };
1082
+ const commit = stamped.slice(0, spec.head.length || 8);
1083
+ return { commit, said: commit === spec.head ? `HEAD ${commit}` : `deployed ${commit} (HEAD has moved on to ${spec.head} since)` };
1084
+ }
1085
+
1062
1086
  /**
1063
1087
  * The whole anonymous `worker:smoke` of a deployed Worker, lifted out of `apps/family` and
1064
1088
  * `apps/music` (task 2145). Per address: `/healthz` settles on THIS commit with the door mounted
@@ -1078,6 +1102,7 @@ export async function runWorkerSmoke(spec: WorkerSmokeSpec): Promise<number> {
1078
1102
  ? [...spec.urls]
1079
1103
  : [...new Set([deployment.previewUrl, deployment.publicUrl].filter((u): u is string => Boolean(u)))];
1080
1104
  const smoke = createSmoke({ base: deployment.publicUrl || (urls[0] as string), ...spec.smokeOptions });
1105
+ const want = expectedCommit(spec);
1081
1106
 
1082
1107
  for (const base of urls) {
1083
1108
  const host = new URL(base).hostname;
@@ -1104,7 +1129,7 @@ export async function runWorkerSmoke(spec: WorkerSmokeSpec): Promise<number> {
1104
1129
  try {
1105
1130
  const read = await steadyHealth<Record<string, unknown>>(
1106
1131
  () => ask("/healthz", healthRead),
1107
- (body) => body.commit === spec.head && body.apiServed === true,
1132
+ (body) => body.commit === want.commit && body.apiServed === true,
1108
1133
  spec.steady,
1109
1134
  );
1110
1135
  health = read.body;
@@ -1120,7 +1145,7 @@ export async function runWorkerSmoke(spec: WorkerSmokeSpec): Promise<number> {
1120
1145
  health.apiServed === true,
1121
1146
  `apiServed ${JSON.stringify(health.apiServed)}${health.apiRefusal ? ` (${health.apiRefusal})` : ""}`,
1122
1147
  );
1123
- smoke.record(`${tag} commit`, health.commit === spec.head, `serving ${JSON.stringify(health.commit)}, HEAD ${spec.head}`);
1148
+ smoke.record(`${tag} commit`, health.commit === want.commit, `serving ${JSON.stringify(health.commit)}, ${want.said}`);
1124
1149
  smoke.record(
1125
1150
  `${tag} publicUrl`,
1126
1151
  health.publicUrl === deployment.publicUrl,
@@ -1180,6 +1205,7 @@ async function accessDoorSmoke(
1180
1205
  log: (line: string) => void,
1181
1206
  ): Promise<number> {
1182
1207
  const { deployment } = spec;
1208
+ const want = expectedCommit(spec);
1183
1209
  const anonymous = spec.anonymousFetch;
1184
1210
  if (!anonymous) {
1185
1211
  smoke.refuseEnvironment("the anonymous fetch", 'door: "access" needs `anonymousFetch` — a fetch that carries NO Access token');
@@ -1199,7 +1225,7 @@ async function accessDoorSmoke(
1199
1225
  let health: Record<string, unknown>;
1200
1226
  let status: number;
1201
1227
  try {
1202
- const read = await steadyHealth<Record<string, unknown>>(() => ask("/healthz", healthRead), (body) => body.commit === spec.head, spec.steady);
1228
+ const read = await steadyHealth<Record<string, unknown>>(() => ask("/healthz", healthRead), (body) => body.commit === want.commit, spec.steady);
1203
1229
  health = read.body;
1204
1230
  status = read.response.status;
1205
1231
  } catch (error) {
@@ -1208,7 +1234,7 @@ async function accessDoorSmoke(
1208
1234
  continue;
1209
1235
  }
1210
1236
  smoke.record(`${tag} healthz`, status === 200 && health.runtime === "worker", `${status}, runtime ${JSON.stringify(health.runtime)}`);
1211
- smoke.record(`${tag} commit`, health.commit === spec.head, `serving ${JSON.stringify(health.commit)}, HEAD ${spec.head}`);
1237
+ smoke.record(`${tag} commit`, health.commit === want.commit, `serving ${JSON.stringify(health.commit)}, ${want.said}`);
1212
1238
  smoke.record(
1213
1239
  `${tag} access`,
1214
1240
  health.access === true,
@@ -60,6 +60,7 @@ import { join } from "node:path";
60
60
  import { liveColumns, liveColumnsQueries, missingColumnStatements, schemaTables } from "cursedops/d1-columns";
61
61
  import { wranglerRows } from "cursedops/d1-import";
62
62
  import { forgeCodeRoot, forgeState } from "cursedops/roots";
63
+ import { DEPLOYED_COMMIT_ENV } from "cursedops/smoke";
63
64
  import { appendLedger, deferral, readShipSetting, type ShipEventLine, type ShipNowWhere, type ShipSetting } from "cursedops/ship";
64
65
  import { type StageLeaseResult, takeStageLease } from "cursedops/stage-lease";
65
66
 
@@ -81,7 +82,7 @@ export function parseWorkerEnv(argv: readonly string[]): WorkerEnv {
81
82
  const value = (joined !== undefined ? joined.slice("--env=".length) : argv[index + 1])?.trim();
82
83
  if (value === "production" || value === "stage") return value;
83
84
  throw new Error(
84
- `unrecognised --env ${JSON.stringify(value ?? "")} — production (no flag) or stage. ` +
85
+ `unrecognized --env ${JSON.stringify(value ?? "")} — production (no flag) or stage. ` +
85
86
  "This refuses rather than defaulting: a typo that meant production would report success against the owner's data.",
86
87
  );
87
88
  }
@@ -977,7 +978,8 @@ export function runWorkerDeploy(spec: WorkerDeploySpec, deps: DeployDeps): Deplo
977
978
  step(`smoke every address this deployment answers on${spec.cpuTail ? ", under a CPU tail" : ""}`);
978
979
  enter("smoke");
979
980
  const smoke = spec.cpuTail ? underCpuTail(spec.cpuTail, spec.env, spec.smoke) : spec.smoke;
980
- const smoked = deps.run(smoke, {});
981
+ // The commit stamped above, not the HEAD the smoke would read minutes later (`DEPLOYED_COMMIT_ENV`).
982
+ const smoked = deps.run(smoke, { [DEPLOYED_COMMIT_ENV]: commit });
981
983
  if (spec.cpuTail && smoked === TAIL_UNATTACHED_EXIT) unmeasured.push("the smoke");
982
984
  else if (smoked !== 0) {
983
985
  return stop(
@@ -1262,7 +1264,7 @@ export interface JudgeOpts {
1262
1264
  scriptName: string;
1263
1265
  /** Paths that are never the static tier. Default {@link DEFAULT_API_PREFIXES}. */
1264
1266
  apiPrefixes?: readonly string[];
1265
- /** Replace the labeller entirely. Default {@link tierRoute}. */
1267
+ /** Replace the labeler entirely. Default {@link tierRoute}. */
1266
1268
  route?: (method: string, path: string) => string | null;
1267
1269
  }
1268
1270
 
@@ -16,7 +16,7 @@
16
16
  * ## Why this order, and why it is a library entry
17
17
  *
18
18
  * `patterns` wrote it first (2026-09-19) because the owner required the rollback to be ONE
19
- * command — a launchd invocation nobody runs monthly and a route id nobody has memorised are not
19
+ * command — a launchd invocation nobody runs monthly and a route id nobody has memorized are not
20
20
  * cheap at the moment somebody needs them — and `collections`, `vault` and `music` copied it.
21
21
  * Deleting the route while nothing listens on the Mac points the household's hostname at a dead
22
22
  * box; so this starts the origin, WAITS for it to answer, and refuses to touch the route if it
@@ -184,7 +184,7 @@ export async function runWorkerRollback(spec: WorkerRollbackSpec, argv: readonly
184
184
  const found = await findWorkerRoute(spec.api, host);
185
185
  log(`route ${found.pattern} → ${found.route ? `${found.script} (${found.route})` : "(none — the hostname already falls through to the Mac)"}`);
186
186
 
187
- log("\n▸ what D1 holds against the Mac's frozen catalogue (the writes a rollback loses):");
187
+ log("\n▸ what D1 holds against the Mac's frozen catalog (the writes a rollback loses):");
188
188
  bun(["run", "scripts/worker-import.ts", "--verify-only"], spec.root);
189
189
 
190
190
  if (argv.includes("--check")) {
@@ -30,7 +30,7 @@
30
30
  * never noticed an extra — the drift this module ends.
31
31
  *
32
32
  * 🔴 **A value that is already single-quoted is written as is.** {@link envFileLines}:
33
- * `collections`' seed serialiser returns `'{"kdf":…}'`, quoting it again wrote an escaped pair
33
+ * `collections`' seed serializer returns `'{"kdf":…}'`, quoting it again wrote an escaped pair
34
34
  * around it, `readEnvFile` stripped one layer, and the stage's master lock came up asking to
35
35
  * ENROLL a new password (found 2026-09-22 by the stage walk, one step past sign-in).
36
36
  */