@vibe-commit/capture 0.10.0 → 0.11.0

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 (81) hide show
  1. package/README.md +24 -0
  2. package/dist/agent_hooks.js +2 -2
  3. package/dist/cli.d.ts +35 -0
  4. package/dist/cli.js +412 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/backfill.js +19 -2
  7. package/dist/commands/backfill.js.map +1 -1
  8. package/dist/commands/finalize.d.ts +37 -1
  9. package/dist/commands/finalize.js +129 -29
  10. package/dist/commands/finalize.js.map +1 -1
  11. package/dist/commands/status.js +7 -0
  12. package/dist/commands/status.js.map +1 -1
  13. package/dist/commands/status_machine.js +5 -0
  14. package/dist/commands/status_machine.js.map +1 -1
  15. package/dist/commands/upload_worker.d.ts +63 -0
  16. package/dist/commands/upload_worker.js +336 -0
  17. package/dist/commands/upload_worker.js.map +1 -0
  18. package/dist/copy/strings.d.ts +23 -7
  19. package/dist/copy/strings.js +40 -7
  20. package/dist/copy/strings.js.map +1 -1
  21. package/dist/drain.d.ts +68 -0
  22. package/dist/drain.js +234 -0
  23. package/dist/drain.js.map +1 -0
  24. package/dist/firing.d.ts +169 -0
  25. package/dist/firing.js +334 -0
  26. package/dist/firing.js.map +1 -0
  27. package/dist/hooks/entry.d.ts +171 -26
  28. package/dist/hooks/entry.js +333 -507
  29. package/dist/hooks/entry.js.map +1 -1
  30. package/dist/hooks/fire_lock.d.ts +36 -87
  31. package/dist/hooks/fire_lock.js +40 -99
  32. package/dist/hooks/fire_lock.js.map +1 -1
  33. package/dist/hooks/post_commit.d.ts +2 -0
  34. package/dist/hooks/post_commit.js +7 -3
  35. package/dist/hooks/post_commit.js.map +1 -1
  36. package/dist/identity.d.ts +32 -0
  37. package/dist/identity.js +38 -0
  38. package/dist/identity.js.map +1 -0
  39. package/dist/index.d.ts +3 -17
  40. package/dist/index.js +59 -450
  41. package/dist/index.js.map +1 -1
  42. package/dist/paths.d.ts +18 -0
  43. package/dist/paths.js +31 -0
  44. package/dist/paths.js.map +1 -1
  45. package/dist/pending_prune.d.ts +2 -2
  46. package/dist/pending_prune.js +2 -2
  47. package/dist/policy.d.ts +17 -0
  48. package/dist/policy.js +7 -0
  49. package/dist/policy.js.map +1 -1
  50. package/dist/post.d.ts +52 -4
  51. package/dist/post.js +59 -21
  52. package/dist/post.js.map +1 -1
  53. package/dist/replay.d.ts +167 -0
  54. package/dist/replay.js +301 -0
  55. package/dist/replay.js.map +1 -0
  56. package/dist/session_targets.d.ts +12 -0
  57. package/dist/session_targets.js +6 -1
  58. package/dist/session_targets.js.map +1 -1
  59. package/dist/spool.d.ts +44 -16
  60. package/dist/spool.js +174 -33
  61. package/dist/spool.js.map +1 -1
  62. package/dist/spool_union.d.ts +18 -2
  63. package/dist/spool_union.js +23 -2
  64. package/dist/spool_union.js.map +1 -1
  65. package/dist/state.js +21 -14
  66. package/dist/state.js.map +1 -1
  67. package/dist/stdin.d.ts +2 -0
  68. package/dist/stdin.js +13 -0
  69. package/dist/stdin.js.map +1 -0
  70. package/dist/uploader_status.d.ts +34 -0
  71. package/dist/uploader_status.js +25 -0
  72. package/dist/uploader_status.js.map +1 -0
  73. package/dist/uploader_view.d.ts +39 -0
  74. package/dist/uploader_view.js +106 -0
  75. package/dist/uploader_view.js.map +1 -0
  76. package/dist/verbs.d.ts +11 -0
  77. package/dist/verbs.js +13 -0
  78. package/dist/verbs.js.map +1 -0
  79. package/dist/version.d.ts +1 -1
  80. package/dist/version.js +1 -1
  81. package/package.json +2 -2
@@ -0,0 +1,336 @@
1
+ /**
2
+ * THE UPLOAD WORKER — U5a of the detached uploader (plan §3.3, §3.9, §3.12).
3
+ *
4
+ * `vibecommit __upload-worker`: a hidden verb (not in `VERBS`, not in help), spawned detached by the hook when it has recorded a firing and
5
+ * no worker is alive (U6 spawns it; until then nothing does). ONE worker per machine and identity (`identity.ts`) holds
6
+ * `uploader.<id>.lock`, walks every session that has a firing log, and replays each session's oldest unconsumed firing ALONE through
7
+ * `replayStep`. The hook never waits for the network again; this process does, with a fixed request timeout and no deadline.
8
+ *
9
+ * ## What bounds it
10
+ * - **One request at a time**, and after each step that made requests a sleep of `max(250 ms, the step's elapsed time)` capped at 30 s
11
+ * (D9), so about half the wall clock is server work. A step is one delta per target, so it is a handful of requests at most.
12
+ * - **Backoff per session** on `later`: 5 s, doubling to 5 min, with jitter. Sessions back off independently; one failing session does not
13
+ * slow the rest.
14
+ * - **No idle linger.** It exits when nothing is owed (release the lock, look at the queue again, and take the lock back only if
15
+ * something arrived in between), after 30 min with no progress and after 60 min in all, so an upgrade takes effect and a stuck worker
16
+ * cannot live on. The next hook respawns it.
17
+ * - **It never waits for a session's writer lock** (a busy session is revisited) and sleeps at least 25 ms after every step that held one,
18
+ * longer than a waiting `finalize` polls, so a waiter can win.
19
+ * - **A newer firing version** stops it after the step in hand: a newer build owns those lines.
20
+ * - **A revoked or missing credential** stops it (`fatal`); the cursor stays, so nothing is lost and the next hook retries.
21
+ *
22
+ * ## What it reports: `uploader.<id>.status`
23
+ * Counts, classes and times only: never a body, a path or a credential. Written atomically after every step. `status` reads it (U7), and
24
+ * the hook reads it for notices (U6). "Behind" is NEVER taken from the worker's own claim; `status` recomputes it from the firing logs.
25
+ *
26
+ * ## ⛔ Static imports are the worker's own. `index.ts` loads this file with a dynamic `import()` that fails closed to exit 0.
27
+ */
28
+ import { writeFileAtomic } from "../atomic_write.js";
29
+ import { drainIdleStep } from "../drain.js";
30
+ import { firingSessions, readCursor, readFirings } from "../firing.js";
31
+ import { unboundedPaceMs } from "../hooks/entry.js";
32
+ import { claimUploader, UPLOADER_BEAT_MS } from "../hooks/fire_lock.js";
33
+ import { identityDigest } from "../identity.js";
34
+ import { uploaderStatusPath } from "../paths.js";
35
+ import { ingestUrl } from "../post.js";
36
+ import { replayStep, REQUEST_TIMEOUT_MS } from "../replay.js";
37
+ /** Backoff on `later` (plan §3.12). */
38
+ export const BACKOFF_FIRST_MS = 5_000;
39
+ export const BACKOFF_MAX_MS = 5 * 60_000;
40
+ /** Pacing (D9): sleep after a step that made requests. The constants and `unboundedPaceMs` live in `hooks/entry.ts`, beside the loops that must honour them. */
41
+ /** Yield after a step that held a session lock: longer than `WRITER_POLL_MS` (20 ms) so a waiter can win. */
42
+ export const YIELD_MS = 25;
43
+ /** Lifetime bounds (plan §3.9). */
44
+ export const NO_PROGRESS_MS = 30 * 60_000;
45
+ export const TOTAL_MS = 60 * 60_000;
46
+ /** How long a pass with nothing to do but wait (busy sessions, backoff) sleeps before looking again. */
47
+ const IDLE_POLL_MS = 1_000;
48
+ /** The last outcomes kept in the status file. */
49
+ const OUTCOMES_KEPT = 20;
50
+ const realSleep = (ms) => new Promise((resolve) => {
51
+ setTimeout(resolve, ms);
52
+ });
53
+ /** `5 s, 10 s, ... 5 min`, with ±25 % jitter. Pure given `random`. */
54
+ export function backoffMs(failures, random) {
55
+ const base = Math.min(BACKOFF_MAX_MS, BACKOFF_FIRST_MS * 2 ** Math.max(0, failures - 1));
56
+ return Math.floor(base * (0.75 + random() * 0.5));
57
+ }
58
+ /** `Retry-After` wins when it is longer than our own backoff, and nothing waits past the backoff ceiling. Pure. */
59
+ export function waitAfterLaterMs(failures, retryAfterMs, random) {
60
+ return Math.min(BACKOFF_MAX_MS, Math.max(backoffMs(failures, random), retryAfterMs ?? 0));
61
+ }
62
+ /** The sleep after a step that made requests: `max(250 ms, elapsed)`, capped. Pure. */
63
+ export function paceMs(elapsedMs) {
64
+ return unboundedPaceMs(elapsedMs);
65
+ }
66
+ /** Did this step put requests on the wire? (`done:nothing-granted` and friends resolved targets and sent nothing.) */
67
+ function madeRequests(step) {
68
+ switch (step.kind) {
69
+ case "more":
70
+ case "later":
71
+ case "threw":
72
+ return true;
73
+ case "fatal":
74
+ return step.why === "rejected";
75
+ case "done":
76
+ // U6b (F2b): a HELD step that put a request on the wire (a 403 capture_not_approved) is paced like any other; `requested` says so.
77
+ return step.class === "delivered" || (step.class === "held" && step.requested === true);
78
+ default:
79
+ return false;
80
+ }
81
+ }
82
+ /** Does `stem` still owe a firing of this identity? A read, no lock. */
83
+ function owes(cfg, stem) {
84
+ return readFirings(cfg.home, stem, readCursor(cfg.home, stem).offset).firings.some((r) => r.firing.id === cfg.identity);
85
+ }
86
+ export async function runUploadWorker(opts) {
87
+ const now = opts.now ?? Date.now;
88
+ const sleep = opts.sleep ?? realSleep;
89
+ const random = opts.random ?? Math.random;
90
+ const noProgressMs = opts.noProgressMs ?? NO_PROGRESS_MS;
91
+ const totalMs = opts.totalMs ?? TOTAL_MS;
92
+ const url = ingestUrl(opts.env);
93
+ if (url === null)
94
+ return "no-url";
95
+ const identity = identityDigest({ url, env: opts.env });
96
+ const cfg = {
97
+ home: opts.home,
98
+ env: opts.env,
99
+ nodeVersion: opts.nodeVersion,
100
+ identity,
101
+ url,
102
+ requestTimeoutMs: opts.requestTimeoutMs ?? REQUEST_TIMEOUT_MS,
103
+ };
104
+ const startedAt = now();
105
+ let lastProgressAt = startedAt;
106
+ const counts = {};
107
+ const outcomes = [];
108
+ // Set when the server rejects a credential; cleared by the next delivery. Read by the hook's `credential_revoked` notice.
109
+ let rejected;
110
+ const failures = new Map();
111
+ const notBefore = new Map();
112
+ // The idle drain (U5c): bytes sent this worker life, and one backoff for the drain as a whole (a failing session rotates to the back).
113
+ const drain = { bytes: 0 };
114
+ let drainFailures = 0;
115
+ let drainNotBefore = 0;
116
+ const writeStatus = (state, exit) => {
117
+ const status = {
118
+ v: 1,
119
+ pid: process.pid,
120
+ startedAt,
121
+ heartbeatAt: now(),
122
+ state,
123
+ ...(exit === undefined ? {} : { exit }),
124
+ counts,
125
+ outcomes,
126
+ ...(rejected === undefined ? {} : { rejected }),
127
+ };
128
+ try {
129
+ writeFileAtomic(uploaderStatusPath(opts.home, identity), `${JSON.stringify(status)}\n`, 0o600);
130
+ }
131
+ catch {
132
+ // A status file that cannot be written must not stop the uploading it reports on.
133
+ }
134
+ };
135
+ const record = (stem, klass, elapsedMs) => {
136
+ counts[klass] = (counts[klass] ?? 0) + 1;
137
+ outcomes.push({ at: now(), class: klass, elapsedMs, session: stem });
138
+ if (outcomes.length > OUTCOMES_KEPT)
139
+ outcomes.splice(0, outcomes.length - OUTCOMES_KEPT);
140
+ };
141
+ for (;;) {
142
+ const claim = claimUploader(opts.home, identity);
143
+ if (claim === null)
144
+ return "not-claimed";
145
+ let lockLost = false;
146
+ // The lock is beaten from a timer, so a live worker is never judged dead however long one request runs.
147
+ const beat = setInterval(() => {
148
+ if (!claim.refresh())
149
+ lockLost = true;
150
+ }, UPLOADER_BEAT_MS);
151
+ beat.unref();
152
+ let exit = null;
153
+ try {
154
+ writeStatus("running");
155
+ while (exit === null) {
156
+ if (lockLost) {
157
+ exit = "lock-lost";
158
+ break;
159
+ }
160
+ if (now() - startedAt >= totalMs) {
161
+ exit = "total";
162
+ break;
163
+ }
164
+ if (now() - lastProgressAt >= noProgressMs) {
165
+ exit = "no-progress";
166
+ break;
167
+ }
168
+ let worked = false;
169
+ let waiting = false;
170
+ let newer = false;
171
+ for (const stem of firingSessions(opts.home)) {
172
+ if ((notBefore.get(stem) ?? 0) > now()) {
173
+ waiting = true;
174
+ continue;
175
+ }
176
+ let step;
177
+ try {
178
+ step = await replayStep(cfg, stem);
179
+ }
180
+ catch {
181
+ // `replayStep` counts its own throws; one that escapes it is a bug, and a worker that dies on it would crash-loop the machine.
182
+ record(stem, "escaped", 0);
183
+ continue;
184
+ }
185
+ switch (step.kind) {
186
+ case "idle":
187
+ case "foreign":
188
+ continue;
189
+ case "busy":
190
+ waiting = true;
191
+ continue;
192
+ case "newer":
193
+ newer = true;
194
+ continue;
195
+ case "done":
196
+ if (step.class === "delivered")
197
+ rejected = undefined;
198
+ record(stem, `done:${step.class}`, step.elapsedMs);
199
+ failures.delete(stem);
200
+ notBefore.delete(stem);
201
+ worked = true;
202
+ lastProgressAt = now();
203
+ break;
204
+ case "more":
205
+ record(stem, "more", step.elapsedMs);
206
+ failures.delete(stem);
207
+ worked = true;
208
+ lastProgressAt = now();
209
+ break;
210
+ case "later": {
211
+ const n = (failures.get(stem) ?? 0) + 1;
212
+ failures.set(stem, n);
213
+ notBefore.set(stem, now() + waitAfterLaterMs(n, step.retryAfterMs, random));
214
+ record(stem, "later", step.elapsedMs);
215
+ waiting = true;
216
+ break;
217
+ }
218
+ case "threw":
219
+ record(stem, `threw:${step.class}`, step.elapsedMs);
220
+ if (step.class === "cursor-unwritable") {
221
+ // U6b (F9c): a cursor that cannot be written re-ran the resolver every ~250 ms for the worker's whole life. Back off like a `later`.
222
+ const n = (failures.get(stem) ?? 0) + 1;
223
+ failures.set(stem, n);
224
+ notBefore.set(stem, now() + waitAfterLaterMs(n, undefined, random));
225
+ waiting = true;
226
+ }
227
+ else {
228
+ worked = true;
229
+ }
230
+ break;
231
+ case "fatal":
232
+ if (step.why === "rejected" && step.fingerprint !== undefined)
233
+ rejected = { at: now(), fingerprint: step.fingerprint };
234
+ record(stem, `fatal:${step.why}`, step.elapsedMs);
235
+ exit = "fatal";
236
+ break;
237
+ case "lost":
238
+ record(stem, "lock-lost", 0);
239
+ exit = "lock-lost";
240
+ break;
241
+ }
242
+ writeStatus("running");
243
+ if (exit !== null)
244
+ break;
245
+ // D9 pacing after requests; otherwise just yield the session lock long enough for a waiter to take it.
246
+ await sleep(madeRequests(step) ? paceMs("elapsedMs" in step ? step.elapsedMs : 0) : YIELD_MS);
247
+ if (lockLost)
248
+ break;
249
+ }
250
+ if (exit !== null)
251
+ break;
252
+ if (newer) {
253
+ exit = "newer-firing";
254
+ break;
255
+ }
256
+ // LIVE FIRINGS OUTRANK THE DRAIN: it runs only when this pass had no live work, and one delta at a time, so the queue is looked at again
257
+ // before the next one.
258
+ if (!worked && !newer) {
259
+ if (drainNotBefore > now()) {
260
+ waiting = true;
261
+ }
262
+ else {
263
+ const step = await drainIdleStep(cfg, drain, now);
264
+ switch (step.kind) {
265
+ case "delivered":
266
+ record("drain", "drain:delivered", step.elapsedMs);
267
+ drainFailures = 0;
268
+ worked = true;
269
+ lastProgressAt = now();
270
+ writeStatus("running");
271
+ await sleep(paceMs(step.elapsedMs));
272
+ break;
273
+ case "later":
274
+ drainFailures += 1;
275
+ drainNotBefore = now() + waitAfterLaterMs(drainFailures, step.retryAfterMs, random);
276
+ record("drain", "drain:later", step.elapsedMs);
277
+ waiting = true;
278
+ writeStatus("running");
279
+ break;
280
+ case "held":
281
+ // F2a/c: a held (403) session costs one request, then the whole drain backs off like a `later`: the same session asked again
282
+ // every second was the flood. The backoff resets on a delivery.
283
+ drainFailures += 1;
284
+ drainNotBefore = now() + waitAfterLaterMs(drainFailures, undefined, random);
285
+ record("drain", "drain:held", step.elapsedMs);
286
+ waiting = true;
287
+ writeStatus("running");
288
+ await sleep(paceMs(step.elapsedMs));
289
+ break;
290
+ case "fatal":
291
+ record("drain", "drain:fatal", step.elapsedMs);
292
+ exit = "fatal";
293
+ break;
294
+ case "idle":
295
+ break;
296
+ }
297
+ if (exit !== null)
298
+ break;
299
+ }
300
+ }
301
+ if (!worked) {
302
+ if (!waiting && ![...firingSessions(opts.home)].some((stem) => owes(cfg, stem))) {
303
+ exit = "idle";
304
+ break;
305
+ }
306
+ await sleep(IDLE_POLL_MS);
307
+ }
308
+ }
309
+ // ⛔ "EXITED" IS WRITTEN WHILE THE LOCK IS STILL OURS (P1's F3). Written after the release, a successor that claimed in the gap and wrote
310
+ // "running" would be overwritten by this stale "exited", and the hook's notices and `status` read this file. Then the test seam, then the release.
311
+ writeStatus("exited", exit ?? "idle");
312
+ opts.beforeRelease?.();
313
+ }
314
+ finally {
315
+ clearInterval(beat);
316
+ // Release FIRST, look at the queue SECOND: the hook appends first and checks the lock second, so at least one side sees the other.
317
+ claim.release();
318
+ }
319
+ if (exit === "idle" && firingSessions(opts.home).some((stem) => owes(cfg, stem))) {
320
+ // A firing arrived between the last look and the release, and the hook that wrote it saw our lock still held and did not spawn.
321
+ continue;
322
+ }
323
+ return exit ?? "idle";
324
+ }
325
+ }
326
+ /** The process entry for `__upload-worker`: real clock, real env. Never throws, never prints, always exits 0. */
327
+ export async function runUploadWorkerMain(env, home) {
328
+ try {
329
+ await runUploadWorker({ home, env, nodeVersion: process.versions.node });
330
+ }
331
+ catch {
332
+ // A detached process has nobody to tell.
333
+ }
334
+ return 0;
335
+ }
336
+ //# sourceMappingURL=upload_worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload_worker.js","sourceRoot":"","sources":["../../src/commands/upload_worker.ts"],"names":[],"mappings":"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"}
@@ -139,6 +139,21 @@ export declare const STATUS: {
139
139
  * changed them to match the screen rather than changing the screen.
140
140
  */
141
141
  readonly repoLabel: "repo";
142
+ /**
143
+ * U7 (plan §3.8): the background uploader's row, machine-wide, under `last sent`. COMPUTED from the firing logs against the ledgers and from the
144
+ * worker's lock, never from the worker's own say-so, so a dead worker with bytes behind is "not running", never "up to date".
145
+ */
146
+ readonly uploadLabel: "upload";
147
+ readonly uploadValue: (view: {
148
+ readonly state: "up_to_date" | "sending" | "waiting_to_retry" | "not_running";
149
+ readonly sessions: number;
150
+ readonly bytes: number;
151
+ readonly ackAge: string | null;
152
+ readonly attemptAge: string | null;
153
+ readonly problem: "no_answer" | "credential" | "error" | null;
154
+ }) => string;
155
+ /** The line under a "not running" row. */
156
+ readonly uploadNotRunning: "It retries on your next agent turn.";
142
157
  /** 0.7.0, D6 · 2A: why this repo is on. One row under `repo`, only inside a repo that is on. */
143
158
  readonly basisLabel: "basis";
144
159
  readonly basisOwner: (owner: string) => string;
@@ -378,18 +393,17 @@ export declare const CONNECT: {
378
393
  /**
379
394
  * ⚠ THE NUMBER AND ITS QUALIFIER ARE ONE UNIT.
380
395
  *
381
- * **12 is `CR-042`'s counted figure** (merged, `6d8ec68`), taken rather than
382
- * recounted: the patterns live in `vibecommit-mcp/src/scrubber/patterns.ts`,
383
- * which is closed source and another chain's repo. `CR-042` exists precisely
384
- * because the draft said 10 — a number is a claim, and this one is counted
385
- * from code.
396
+ * **13 is the server's counted figure since mcp #174** (a PEM `private_key` pattern joined the twelve `CR-042` counted, merged `6d8ec68`,
397
+ * so the earlier text said 12). Taken rather than recounted: the patterns live in `vibecommit-mcp/src/scrubber/patterns.ts`, which is closed
398
+ * source and another chain's repo. `CR-042` exists precisely because the draft said 10 — a number is a claim, and this one is counted from
399
+ * code. Alexander's OK for the change: 2026-11-06 04:35Z (relayed by P1); the web copy (`disclosure.ts`, digest-pinned) changes in lockstep.
386
400
  *
387
401
  * The qualifier is as load-bearing as the number. Without *"it is a pattern
388
402
  * list, not a general secret detector"* and *"a secret it has no pattern for
389
403
  * is uploaded"*, the count reads as PROTECTION — which is the reassurance D65
390
404
  * §DR7 bans, arriving as arithmetic instead of as an adjective.
391
405
  */
392
- readonly consentScrubber: "A scrubber removes 12 known secret formats (AWS keys, GitHub tokens, and similar). It is a pattern list, not a general secret detector: a secret it has no pattern for is uploaded. Command output is not path-filtered.";
406
+ readonly consentScrubber: "A scrubber removes 13 known secret formats (AWS keys, GitHub tokens, and similar). It is a pattern list, not a general secret detector: a secret it has no pattern for is uploaded. Command output is not path-filtered.";
393
407
  /**
394
408
  * ⛔ **THE SECOND DATA CLASS, AND THE DISCLOSURE DID NOT MENTION IT AT ALL —
395
409
  * `T15`, `D190`.**
@@ -415,7 +429,7 @@ export declare const CONNECT: {
415
429
  * rightly: `post_commit.ts` gathered the list and it stayed in the spool. From 0.10.0 `capSpool` also returns `files` and the
416
430
  * delta that carries `x-commits` carries the list too, for a GRANTED REPOSITORY only (never a folder). The paths are git's own
417
431
  * (`git diff-tree -z --name-only`), unchanged, and the transcript scrubber does not run on them, which the sentence says
418
- * because "a scrubber removes 12 known secret formats" two paragraphs up would otherwise read as covering them. **Not
432
+ * because "a scrubber removes 13 known secret formats" two paragraphs up would otherwise read as covering them. **Not
419
433
  * describing the paths as uploaded would now be the FALSE DISCLOSURE, which is as much a claims defect as describing what
420
434
  * does not leave.** The committer DATE still stays local.
421
435
  *
@@ -898,6 +912,8 @@ export declare const BACKFILL: {
898
912
  readonly parentLabel: "outside a repository";
899
913
  readonly parentTouchedNothing: "touched nothing you chose";
900
914
  readonly parentBusy: "another process was writing it";
915
+ /** U5b: an earlier turn of the session was recorded but could not be sent yet, so the session is not closed over it. */
916
+ readonly replayStopped: "an earlier turn could not be sent yet";
901
917
  readonly parentFolderRefused: "the folder's repository belongs to someone else";
902
918
  /** Skip reasons. */
903
919
  readonly overSize: "over 50 MiB";
@@ -233,6 +233,38 @@ export const STATUS = {
233
233
  * changed them to match the screen rather than changing the screen.
234
234
  */
235
235
  repoLabel: "repo",
236
+ /**
237
+ * U7 (plan §3.8): the background uploader's row, machine-wide, under `last sent`. COMPUTED from the firing logs against the ledgers and from the
238
+ * worker's lock, never from the worker's own say-so, so a dead worker with bytes behind is "not running", never "up to date".
239
+ */
240
+ uploadLabel: "upload",
241
+ uploadValue: (view) => {
242
+ const size = view.bytes < 1024 ? `${view.bytes} ${view.bytes === 1 ? "byte" : "bytes"}` : view.bytes < 1048576 ? `${(view.bytes / 1024).toFixed(1)} KiB` : `${(view.bytes / 1048576).toFixed(1)} MiB`;
243
+ // "Behind" is RECORDED turns not yet sent (firings the hooks wrote that no worker has consumed): not the idle-drain backlog of sessions with no
244
+ // unconsumed firing, which this row does not count, and it includes firings of another server or credential.
245
+ const behind = `${view.sessions} ${view.sessions === 1 ? "session" : "sessions"} with unsent turns, ${size}`;
246
+ const ack = view.ackAge === null ? "nothing acknowledged yet" : `last acknowledged ${view.ackAge}`;
247
+ const why = view.problem === "no_answer"
248
+ ? "the server did not answer"
249
+ : view.problem === "credential"
250
+ ? "the server did not accept this machine's credential"
251
+ : view.problem === "error"
252
+ ? "it stopped on an error"
253
+ : null;
254
+ const attempt = view.attemptAge === null || why === null ? null : `last attempt ${view.attemptAge}: ${why}`;
255
+ switch (view.state) {
256
+ case "up_to_date":
257
+ return view.ackAge === null ? "up to date" : `up to date · ${ack}`;
258
+ case "sending":
259
+ return `sending · ${behind} · ${ack}`;
260
+ case "waiting_to_retry":
261
+ return `waiting to retry · ${behind} · ${attempt ?? ack}`;
262
+ case "not_running":
263
+ return `not running · ${behind} · ${attempt ?? ack}`;
264
+ }
265
+ },
266
+ /** The line under a "not running" row. */
267
+ uploadNotRunning: "It retries on your next agent turn.",
236
268
  /** 0.7.0, D6 · 2A: why this repo is on. One row under `repo`, only inside a repo that is on. */
237
269
  basisLabel: "basis",
238
270
  basisOwner: (owner) => `owner grant ${owner}/`,
@@ -522,18 +554,17 @@ export const CONNECT = {
522
554
  /**
523
555
  * ⚠ THE NUMBER AND ITS QUALIFIER ARE ONE UNIT.
524
556
  *
525
- * **12 is `CR-042`'s counted figure** (merged, `6d8ec68`), taken rather than
526
- * recounted: the patterns live in `vibecommit-mcp/src/scrubber/patterns.ts`,
527
- * which is closed source and another chain's repo. `CR-042` exists precisely
528
- * because the draft said 10 — a number is a claim, and this one is counted
529
- * from code.
557
+ * **13 is the server's counted figure since mcp #174** (a PEM `private_key` pattern joined the twelve `CR-042` counted, merged `6d8ec68`,
558
+ * so the earlier text said 12). Taken rather than recounted: the patterns live in `vibecommit-mcp/src/scrubber/patterns.ts`, which is closed
559
+ * source and another chain's repo. `CR-042` exists precisely because the draft said 10 — a number is a claim, and this one is counted from
560
+ * code. Alexander's OK for the change: 2026-11-06 04:35Z (relayed by P1); the web copy (`disclosure.ts`, digest-pinned) changes in lockstep.
530
561
  *
531
562
  * The qualifier is as load-bearing as the number. Without *"it is a pattern
532
563
  * list, not a general secret detector"* and *"a secret it has no pattern for
533
564
  * is uploaded"*, the count reads as PROTECTION — which is the reassurance D65
534
565
  * §DR7 bans, arriving as arithmetic instead of as an adjective.
535
566
  */
536
- consentScrubber: "A scrubber removes 12 known secret formats (AWS keys, GitHub tokens, and similar). It is a pattern list, not a general secret detector: a secret it has no pattern for is uploaded. Command output is not path-filtered.",
567
+ consentScrubber: "A scrubber removes 13 known secret formats (AWS keys, GitHub tokens, and similar). It is a pattern list, not a general secret detector: a secret it has no pattern for is uploaded. Command output is not path-filtered.",
537
568
  /**
538
569
  * ⛔ **THE SECOND DATA CLASS, AND THE DISCLOSURE DID NOT MENTION IT AT ALL —
539
570
  * `T15`, `D190`.**
@@ -559,7 +590,7 @@ export const CONNECT = {
559
590
  * rightly: `post_commit.ts` gathered the list and it stayed in the spool. From 0.10.0 `capSpool` also returns `files` and the
560
591
  * delta that carries `x-commits` carries the list too, for a GRANTED REPOSITORY only (never a folder). The paths are git's own
561
592
  * (`git diff-tree -z --name-only`), unchanged, and the transcript scrubber does not run on them, which the sentence says
562
- * because "a scrubber removes 12 known secret formats" two paragraphs up would otherwise read as covering them. **Not
593
+ * because "a scrubber removes 13 known secret formats" two paragraphs up would otherwise read as covering them. **Not
563
594
  * describing the paths as uploaded would now be the FALSE DISCLOSURE, which is as much a claims defect as describing what
564
595
  * does not leave.** The committer DATE still stays local.
565
596
  *
@@ -1066,6 +1097,8 @@ export const BACKFILL = {
1066
1097
  parentLabel: "outside a repository",
1067
1098
  parentTouchedNothing: "touched nothing you chose",
1068
1099
  parentBusy: "another process was writing it",
1100
+ /** U5b: an earlier turn of the session was recorded but could not be sent yet, so the session is not closed over it. */
1101
+ replayStopped: "an earlier turn could not be sent yet",
1069
1102
  parentFolderRefused: "the folder's repository belongs to someone else",
1070
1103
  /** Skip reasons. */
1071
1104
  overSize: "over 50 MiB",