cursedops 0.21.0 → 0.21.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/smoke.ts +74 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedops",
3
- "version": "0.21.0",
3
+ "version": "0.21.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 check that a launchd job imports only what the LOCKED versions of its packages export (a lib-preview overlay's extra subpath or name is a job that dies at the next forced reinstall), 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/smoke.ts CHANGED
@@ -932,6 +932,54 @@ export const HEALTH_READ_MAX_TIME_SEC = 10;
932
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
+ /** How long one address's between-versions answers are asked again before they are judged. */
936
+ export const BETWEEN_VERSIONS_MS = 60_000;
937
+
938
+ /**
939
+ * One address's allowance for answers of the edge BETWEEN VERSIONS — a 5xx, or no answer at all —
940
+ * after `/healthz` has settled. Shared by every question asked of that address, so a Worker that
941
+ * really 500s costs one window, not one per route.
942
+ *
943
+ * 🔴 Measured 2026-10-28 (forge task 3882): auth's production-first smoke settled `/healthz` on
944
+ * bea42b2f three reads in a row, and in the next seconds — six `secret put`s, six versions —
945
+ * `/account` answered 500 on the preview while the public host gave 200, and five census routes
946
+ * answered 500 on the public host while the preview gave 401. Judged once each, that rolled back a
947
+ * deploy whose stage, on the same bytes, passed all 28 checks minutes later. `/healthz` reads no
948
+ * session; the routes behind the D1 snapshot were still on the turning edge.
949
+ *
950
+ * Only the window's shapes are asked again. A REAL wrong answer — a private route that answers 200,
951
+ * a shell that is JSON — is judged on its first read, and a 5xx that outlasts the window is red.
952
+ */
953
+ interface VersionWindow {
954
+ /** Ask until the answer is not between versions, or the window is spent; the LAST answer is judged. */
955
+ settle<T>(ask: () => Promise<T>, between: (answer: T) => boolean): Promise<{ answer: T; first: T | null; waitedMs: number }>;
956
+ }
957
+
958
+ function versionWindow(options: SteadyOptions = {}): VersionWindow {
959
+ const sleep = options.sleep ?? ((ms: number) => new Promise<void>((done) => setTimeout(done, ms)));
960
+ const now = options.now ?? Date.now;
961
+ let opened: number | null = null;
962
+ return {
963
+ async settle(ask, between) {
964
+ const asked = now();
965
+ let first: Awaited<ReturnType<typeof ask>> | null = null;
966
+ for (;;) {
967
+ const answer = await ask();
968
+ if (!between(answer)) return { answer, first, waitedMs: now() - asked };
969
+ first ??= answer;
970
+ opened ??= now();
971
+ if (now() - opened >= BETWEEN_VERSIONS_MS) return { answer, first, waitedMs: now() - asked };
972
+ await sleep(options.retryMs ?? 3_000);
973
+ }
974
+ },
975
+ };
976
+ }
977
+
978
+ /** `, after <first> for Ns` — the row's note that an answer was asked again, so a settle is never silent. */
979
+ function settledNote(first: unknown, waitedMs: number): string {
980
+ return first === null ? "" : ` (${first} first, between versions — asked again for ${Math.round(waitedMs / 1000)}s)`;
981
+ }
982
+
935
983
  /** One question a deploy must answer the NEW way — `null` when it does, else why not. */
936
984
  export interface ConvergenceProbe {
937
985
  name: string;
@@ -1154,14 +1202,21 @@ export async function runWorkerSmoke(spec: WorkerSmokeSpec): Promise<number> {
1154
1202
 
1155
1203
  // Each probe is anonymous and refused at the gate, so it is replay-safe whatever its method,
1156
1204
  // and one that gets no answer even then is this check's red — never a crash of the whole smoke.
1205
+ const versions = versionWindow(spec.steady);
1157
1206
  const open: string[] = [];
1207
+ const settled: string[] = [];
1158
1208
  for (const route of spec.census) {
1159
1209
  const probe: EdgeRequestInit = { method: route.method, replaySafe: true };
1160
- const answered = await ask(route.path, probe).then(
1161
- (res) => res.status,
1162
- (error: Error) => `no answer (${error.message})`,
1210
+ const { answer, first, waitedMs } = await versions.settle(
1211
+ () =>
1212
+ ask(route.path, probe).then(
1213
+ (res): number | string => res.status,
1214
+ (error: Error) => `no answer (${error.message})`,
1215
+ ),
1216
+ (a) => typeof a === "string" || a >= 500,
1163
1217
  );
1164
- if (answered !== 401) open.push(`${route.method} ${route.path} → ${answered}`);
1218
+ if (answer !== 401) open.push(`${route.method} ${route.path} → ${answer}${settledNote(first, waitedMs)}`);
1219
+ else if (first !== null) settled.push(`${route.method} ${route.path}${settledNote(first, waitedMs)}`);
1165
1220
  }
1166
1221
  smoke.record(
1167
1222
  `${tag} origin-gate`,
@@ -1169,7 +1224,7 @@ export async function runWorkerSmoke(spec: WorkerSmokeSpec): Promise<number> {
1169
1224
  spec.census.length === 0
1170
1225
  ? "🔴 the route census is EMPTY, so this proves nothing"
1171
1226
  : open.length === 0
1172
- ? `all ${spec.census.length} private routes refuse an anonymous caller`
1227
+ ? `all ${spec.census.length} private routes refuse an anonymous caller${settled.length ? ` — ${settled.join(", ")}` : ""}`
1173
1228
  : `🔴 ${open.join(", ")}`,
1174
1229
  );
1175
1230
 
@@ -1177,15 +1232,25 @@ export async function runWorkerSmoke(spec: WorkerSmokeSpec): Promise<number> {
1177
1232
  await spec.extraChecks?.({ tag, address, ask, health, record: (check, ok, detail) => smoke.record(check, ok, detail) });
1178
1233
 
1179
1234
  for (const path of spec.shellPaths) {
1180
- const res = await ask(path, { headers: { accept: "text/html" } });
1235
+ const { answer: res, first, waitedMs } = await versions.settle(
1236
+ () => ask(path, { headers: { accept: "text/html" } }),
1237
+ (r) => r.status >= 500,
1238
+ );
1181
1239
  const type = res.headers.get("content-type") ?? "";
1182
- smoke.record(`${tag} shell ${path}`, res.status === 200 && type.includes("text/html"), `${res.status} ${type}`);
1240
+ smoke.record(
1241
+ `${tag} shell ${path}`,
1242
+ res.status === 200 && type.includes("text/html"),
1243
+ `${res.status} ${type}${settledNote(first?.status ?? null, waitedMs)}`,
1244
+ );
1183
1245
  }
1184
- const miss = await ask("/assets/index-no-such-bundle.js");
1246
+ const { answer: miss, first, waitedMs } = await versions.settle(
1247
+ () => ask("/assets/index-no-such-bundle.js"),
1248
+ (r) => r.status >= 500,
1249
+ );
1185
1250
  smoke.record(
1186
1251
  `${tag} missing-bundle`,
1187
1252
  miss.status === 404 && !(miss.headers.get("content-type") ?? "").includes("text/html"),
1188
- `a missing bundle is a miss — ${miss.status}`,
1253
+ `a missing bundle is a miss — ${miss.status}${settledNote(first?.status ?? null, waitedMs)}`,
1189
1254
  );
1190
1255
  }
1191
1256
  return await smoke.settle();