cursedops 0.10.23 → 0.10.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedops",
3
- "version": "0.10.23",
3
+ "version": "0.10.24",
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": {
@@ -89,19 +89,40 @@ export interface SecretSync {
89
89
  after: string[];
90
90
  }
91
91
 
92
+ /** Tries per put. A put is idempotent, so a retry can only turn a network blip into a success. */
93
+ export const SECRET_PUT_ATTEMPTS = 3;
94
+
92
95
  /**
93
96
  * Apply `plan`: put every value, delete every extra, then READ THE ACCOUNT BACK and fail unless it
94
97
  * holds exactly the set. Refuses without touching anything when the plan has `absent` keys.
98
+ *
99
+ * 🔴 **A failed put is tried again, up to {@link SECRET_PUT_ATTEMPTS} times.** On 2026-09-28 one
100
+ * `wrangler secret put` of three hit `fetch failed` and the whole deploy stopped with
101
+ * collections-stage holding a Worker and no secrets — serving nothing — for a blip the next
102
+ * attempt would have cleared. The read-back is still the verdict.
95
103
  */
96
- export async function syncSecrets(plan: SecretPlan, ops: SecretOps): Promise<SecretSync> {
104
+ export async function syncSecrets(
105
+ plan: SecretPlan,
106
+ ops: SecretOps,
107
+ options: { attempts?: number; pause?: (ms: number) => Promise<void> } = {},
108
+ ): Promise<SecretSync> {
109
+ const attempts = Math.max(1, options.attempts ?? SECRET_PUT_ATTEMPTS);
110
+ const pause = options.pause ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
97
111
  const lines: string[] = [];
98
112
  if (plan.absent.length > 0) {
99
113
  return { ok: false, lines: [`the secrets file has no ${plan.absent.join(" and no ")} — nothing was changed`], after: await ops.held() };
100
114
  }
101
115
  let bad = 0;
102
116
  for (const { name, from, value } of plan.put) {
103
- const ok = await ops.put(name, value);
104
- lines.push(`put ${name}${from === name ? "" : ` (from ${from})`} (${value.length} bytes) — ${ok ? "ok" : "FAILED"}`);
117
+ let ok = false;
118
+ let tries = 0;
119
+ while (!ok && tries < attempts) {
120
+ if (tries > 0) await pause(2_000 * tries);
121
+ ok = await ops.put(name, value);
122
+ tries++;
123
+ }
124
+ const retried = tries > 1 ? ` after ${tries} attempts` : "";
125
+ lines.push(`put ${name}${from === name ? "" : ` (from ${from})`} (${value.length} bytes) — ${ok ? "ok" : "FAILED"}${retried}`);
105
126
  if (!ok) bad++;
106
127
  }
107
128
  for (const name of plan.remove) {