@vibe-commit/capture 0.6.1 → 0.8.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 (172) hide show
  1. package/README.md +191 -9
  2. package/dist/account_not_here.d.ts +15 -0
  3. package/dist/account_not_here.js +25 -0
  4. package/dist/account_not_here.js.map +1 -0
  5. package/dist/agent_hooks.d.ts +8 -0
  6. package/dist/agent_hooks.js +31 -1
  7. package/dist/agent_hooks.js.map +1 -1
  8. package/dist/agents/claude_code.d.ts +34 -0
  9. package/dist/agents/claude_code.js +141 -8
  10. package/dist/agents/claude_code.js.map +1 -1
  11. package/dist/agents/codex_cli.d.ts +13 -0
  12. package/dist/agents/codex_cli.js +241 -2
  13. package/dist/agents/codex_cli.js.map +1 -1
  14. package/dist/agents/cursor.d.ts +34 -0
  15. package/dist/agents/cursor.js +115 -1
  16. package/dist/agents/cursor.js.map +1 -1
  17. package/dist/agents/session_listing.d.ts +29 -0
  18. package/dist/agents/session_listing.js +49 -0
  19. package/dist/agents/session_listing.js.map +1 -0
  20. package/dist/agents/types.d.ts +118 -8
  21. package/dist/agents/types.js +17 -3
  22. package/dist/agents/types.js.map +1 -1
  23. package/dist/commands/approval_pending.d.ts +9 -0
  24. package/dist/commands/approval_pending.js +23 -0
  25. package/dist/commands/approval_pending.js.map +1 -0
  26. package/dist/commands/auth.d.ts +7 -0
  27. package/dist/commands/auth.js +52 -11
  28. package/dist/commands/auth.js.map +1 -1
  29. package/dist/commands/backfill.d.ts +160 -0
  30. package/dist/commands/backfill.js +748 -0
  31. package/dist/commands/backfill.js.map +1 -0
  32. package/dist/commands/browser_credential.d.ts +6 -2
  33. package/dist/commands/browser_credential.js +34 -14
  34. package/dist/commands/browser_credential.js.map +1 -1
  35. package/dist/commands/connect.d.ts +146 -0
  36. package/dist/commands/connect.js +216 -117
  37. package/dist/commands/connect.js.map +1 -1
  38. package/dist/commands/connect_account.d.ts +135 -0
  39. package/dist/commands/connect_account.js +321 -0
  40. package/dist/commands/connect_account.js.map +1 -0
  41. package/dist/commands/connect_picker.d.ts +27 -0
  42. package/dist/commands/connect_picker.js +549 -0
  43. package/dist/commands/connect_picker.js.map +1 -0
  44. package/dist/commands/context.d.ts +16 -0
  45. package/dist/commands/context.js +27 -0
  46. package/dist/commands/context.js.map +1 -1
  47. package/dist/commands/credential_problem.d.ts +15 -4
  48. package/dist/commands/credential_problem.js +29 -22
  49. package/dist/commands/credential_problem.js.map +1 -1
  50. package/dist/commands/device_credential.d.ts +5 -1
  51. package/dist/commands/device_credential.js +17 -14
  52. package/dist/commands/device_credential.js.map +1 -1
  53. package/dist/commands/finalize.d.ts +95 -0
  54. package/dist/commands/finalize.js +249 -74
  55. package/dist/commands/finalize.js.map +1 -1
  56. package/dist/commands/finish_mint.d.ts +30 -6
  57. package/dist/commands/finish_mint.js +26 -6
  58. package/dist/commands/finish_mint.js.map +1 -1
  59. package/dist/commands/report.js +38 -30
  60. package/dist/commands/report.js.map +1 -1
  61. package/dist/commands/scan.d.ts +162 -0
  62. package/dist/commands/scan.js +507 -0
  63. package/dist/commands/scan.js.map +1 -0
  64. package/dist/commands/status.d.ts +19 -1
  65. package/dist/commands/status.js +232 -26
  66. package/dist/commands/status.js.map +1 -1
  67. package/dist/commands/status_hooks.d.ts +14 -0
  68. package/dist/commands/status_hooks.js +81 -0
  69. package/dist/commands/status_hooks.js.map +1 -0
  70. package/dist/commands/status_machine.d.ts +27 -0
  71. package/dist/commands/status_machine.js +210 -0
  72. package/dist/commands/status_machine.js.map +1 -0
  73. package/dist/commands/sync_reason.d.ts +17 -0
  74. package/dist/commands/sync_reason.js +25 -0
  75. package/dist/commands/sync_reason.js.map +1 -0
  76. package/dist/commands/why.js +44 -31
  77. package/dist/commands/why.js.map +1 -1
  78. package/dist/consent.d.ts +217 -0
  79. package/dist/consent.js +400 -1
  80. package/dist/consent.js.map +1 -1
  81. package/dist/copy/error_codes.d.ts +222 -0
  82. package/dist/copy/error_codes.js +105 -0
  83. package/dist/copy/error_codes.js.map +1 -0
  84. package/dist/copy/index.d.ts +2 -2
  85. package/dist/copy/index.js +2 -2
  86. package/dist/copy/index.js.map +1 -1
  87. package/dist/copy/strings.d.ts +617 -30
  88. package/dist/copy/strings.js +741 -36
  89. package/dist/copy/strings.js.map +1 -1
  90. package/dist/copy/time.d.ts +11 -0
  91. package/dist/copy/time.js +18 -0
  92. package/dist/copy/time.js.map +1 -1
  93. package/dist/credential.d.ts +5 -1
  94. package/dist/credential.js +2 -2
  95. package/dist/credential.js.map +1 -1
  96. package/dist/git.d.ts +43 -0
  97. package/dist/git.js +95 -0
  98. package/dist/git.js.map +1 -1
  99. package/dist/grants_sync.d.ts +165 -0
  100. package/dist/grants_sync.js +289 -0
  101. package/dist/grants_sync.js.map +1 -0
  102. package/dist/grants_verb.d.ts +70 -0
  103. package/dist/grants_verb.js +121 -0
  104. package/dist/grants_verb.js.map +1 -0
  105. package/dist/hooks/entry.d.ts +18 -1
  106. package/dist/hooks/entry.js +117 -14
  107. package/dist/hooks/entry.js.map +1 -1
  108. package/dist/hooks/post_commit.js +3 -3
  109. package/dist/hooks/post_commit.js.map +1 -1
  110. package/dist/hooks/post_rewrite.js +6 -4
  111. package/dist/hooks/post_rewrite.js.map +1 -1
  112. package/dist/hooks/session_grants.d.ts +59 -0
  113. package/dist/hooks/session_grants.js +91 -0
  114. package/dist/hooks/session_grants.js.map +1 -0
  115. package/dist/hooks/session_start.d.ts +114 -0
  116. package/dist/hooks/session_start.js +243 -0
  117. package/dist/hooks/session_start.js.map +1 -0
  118. package/dist/index.d.ts +1 -1
  119. package/dist/index.js +76 -11
  120. package/dist/index.js.map +1 -1
  121. package/dist/install.d.ts +57 -0
  122. package/dist/install.js +54 -20
  123. package/dist/install.js.map +1 -1
  124. package/dist/json.d.ts +32 -9
  125. package/dist/json.js +35 -8
  126. package/dist/json.js.map +1 -1
  127. package/dist/machine_state.d.ts +52 -0
  128. package/dist/machine_state.js +63 -0
  129. package/dist/machine_state.js.map +1 -0
  130. package/dist/oauth/retire.d.ts +10 -0
  131. package/dist/oauth/retire.js +13 -0
  132. package/dist/oauth/retire.js.map +1 -1
  133. package/dist/oauth/session.d.ts +1 -0
  134. package/dist/org_pending.d.ts +52 -0
  135. package/dist/org_pending.js +109 -0
  136. package/dist/org_pending.js.map +1 -0
  137. package/dist/paths.d.ts +70 -0
  138. package/dist/paths.js +90 -0
  139. package/dist/paths.js.map +1 -1
  140. package/dist/pending_revoke.d.ts +117 -0
  141. package/dist/pending_revoke.js +233 -0
  142. package/dist/pending_revoke.js.map +1 -0
  143. package/dist/pending_revoke_file.d.ts +55 -0
  144. package/dist/pending_revoke_file.js +74 -0
  145. package/dist/pending_revoke_file.js.map +1 -0
  146. package/dist/picker.d.ts +208 -0
  147. package/dist/picker.js +526 -0
  148. package/dist/picker.js.map +1 -0
  149. package/dist/picker_checklist.d.ts +141 -0
  150. package/dist/picker_checklist.js +411 -0
  151. package/dist/picker_checklist.js.map +1 -0
  152. package/dist/policy.d.ts +70 -1
  153. package/dist/policy.js +68 -2
  154. package/dist/policy.js.map +1 -1
  155. package/dist/post.d.ts +91 -18
  156. package/dist/post.js +259 -27
  157. package/dist/post.js.map +1 -1
  158. package/dist/state.d.ts +6 -0
  159. package/dist/state.js +38 -1
  160. package/dist/state.js.map +1 -1
  161. package/dist/system_message.d.ts +32 -2
  162. package/dist/system_message.js +47 -5
  163. package/dist/system_message.js.map +1 -1
  164. package/dist/term.d.ts +28 -2
  165. package/dist/term.js +23 -2
  166. package/dist/term.js.map +1 -1
  167. package/dist/verb_help.d.ts +13 -0
  168. package/dist/verb_help.js +40 -0
  169. package/dist/verb_help.js.map +1 -0
  170. package/dist/version.d.ts +1 -1
  171. package/dist/version.js +1 -1
  172. package/package.json +5 -9
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pending revokes (slice 2, S4, R3-11): the WRITE-AHEAD record of a credential `g` has made and not yet been told to keep.
3
+ *
4
+ * ```
5
+ * addPendingRevoke(home, credential, endpoint, now) at the mint, BEFORE anything else: refuses (never overwrites) when it cannot
6
+ * removePendingRevoke(home, token) after the revoke, or after the credential is kept: re-reads, removes ONE row
7
+ * revokeHeld(home, token, fetch) one best-effort revoke (3 s) of a row, to ITS recorded origin
8
+ * retryPendingRevokes(home, env, fetch, now) every user-facing verb, once, at its start: one 3 s bound across all rows
9
+ * readPendingRevokes(home) how many are waiting (for `status`'s `pending` count)
10
+ * ```
11
+ *
12
+ * ## ⛔ WHY IT IS WRITE-AHEAD
13
+ *
14
+ * `g` is the one place a credential is made ahead of consent (exception 3). So the credential lives in MEMORY until the yes:
15
+ * `credentials.json` does not exist before it, and the row below is written the instant the server hands the token over. A kill, a
16
+ * second Ctrl+C, a SIGHUP at the y/N or an unexpected throw therefore leaves a ROW (which the next verb revokes), never a live
17
+ * credential on disk that nothing remembers. The yes commits in this order: the grants, then `credentials.json`, then the row's
18
+ * deletion; a retry skips and drops any row whose token IS the current credential, so a kill between the last two never revokes what
19
+ * the user kept.
20
+ *
21
+ * ## ⛔ THE FILE HOLDS A BEARER TOKEN — the same exposure class as `credentials.json`, held to the same rules
22
+ * - written mode 0600 under the 0700 directory, temp + rename;
23
+ * - READ through `readSecretFile`: a file or folder others can read is refused, and an insecure or unreadable file is NEVER
24
+ * overwritten (the writer refuses, and `g` fails closed);
25
+ * - never printed, logged or returned: `status` counts the rows and shows nothing else, `--json` included;
26
+ * - a hook never touches it (only the CLI verbs import this module; `test/connect-account.test.ts` walks the hooks' import graph).
27
+ *
28
+ * ## ⛔ A ROW IS NEVER DROPPED UNREVOKED, except by the clock
29
+ * - it leaves on a 2xx, a 401 (the server does not accept it: already dead) or a 404 (a server that cannot revoke);
30
+ * - or at `minted_at` + 91 days: a token that was never on disk was never used, and an ingest credential ends 90 days unused;
31
+ * - there is no row cap that drops one: at the cap `addPendingRevoke` REFUSES, and `g` fails closed.
32
+ *
33
+ * ## ⛔ THE ORIGIN IS THE ROW'S
34
+ *
35
+ * A row records the issuing server's origin and a retry goes ONLY there (https, or http on loopback: `isAllowedIngestUrl`), never to
36
+ * wherever `VIBECOMMIT_MCP_URL` points today.
37
+ */
38
+ import { IngestCredential, type SecretFileRead } from "./credential.js";
39
+ import { type Queue } from "./pending_revoke_file.js";
40
+ import { REVOKE_TIMEOUT_MS } from "./oauth/retire.js";
41
+ export { readPendingRevokes, type PendingRevoke } from "./pending_revoke_file.js";
42
+ /** More rows than `g` can produce in earnest: past it `g` refuses rather than drop one. */
43
+ export declare const MAX_ROWS = 8;
44
+ /** One best-effort revoke, and the bound on a verb's whole retry. */
45
+ export { REVOKE_TIMEOUT_MS };
46
+ export type AddRevoke = {
47
+ readonly kind: "written";
48
+ }
49
+ /** The file is there but is not safe to read or write (others can read it, or it is not what this client wrote). It was not touched. */
50
+ | {
51
+ readonly kind: "refused";
52
+ readonly read: Extract<Queue, {
53
+ kind: "refused";
54
+ }>["read"];
55
+ }
56
+ /** `MAX_ROWS` unrevoked rows already wait. None was dropped. */
57
+ | {
58
+ readonly kind: "full";
59
+ }
60
+ /** The write itself failed. */
61
+ | {
62
+ readonly kind: "failed";
63
+ };
64
+ /**
65
+ * Write the row for a credential that has just been made. Never overwrites a file it cannot read, never drops a row.
66
+ * `lease: true` is `g` holding the credential at its y/N: the row carries `{ pid, lease_until }` (30 minutes) so another verb's retry
67
+ * leaves it alone while it is kept; every other writer (a test, an orphaned row) has no lease and is retried at once.
68
+ */
69
+ export declare function addPendingRevoke(home: string, credential: IngestCredential, endpoint: string, now: Date, options?: {
70
+ readonly lease?: boolean;
71
+ }): AddRevoke;
72
+ /** Remove ONE row, re-reading just before the write so a row queued in between survives. Deletes the file when it is empty. Never throws. */
73
+ export declare function removePendingRevoke(home: string, token: string): void;
74
+ /** Give the row up to the retry: `g` is no longer holding the credential, so the next verb may revoke it. Re-reads before it writes. */
75
+ export declare function releaseLease(home: string, token: string): void;
76
+ /**
77
+ * Is the row for `token` still there? `g` asks just before it commits, and the three answers are different things:
78
+ * - `held`: the row is in a queue this client can use;
79
+ * - `gone`: the queue is readable and the row is not in it — another verb settled (revoked) it;
80
+ * - `unusable`: the queue is insecure or unreadable. Nobody revoked anything; this client can neither keep nor settle the record.
81
+ */
82
+ export declare function holdsRow(home: string, token: string): "held" | "gone" | "unusable";
83
+ /**
84
+ * What one best-effort revoke of the credential `g` holds came to:
85
+ * - `revoked`: a 2xx, a 401 or a 404 — from the row's recorded origin when a readable row is there, and otherwise from the
86
+ * credential IN MEMORY at the origin that minted it. Never "revoked" for any other reason;
87
+ * - `waiting`: a readable row is there and the revoke did not land; the next verb retries it;
88
+ * - `orphan`: no usable row (it is gone, or the queue became unreadable or insecure) AND the in-memory revoke did not land: the
89
+ * credential may be live and nothing retries it. `insecure` carries the queue file's problem to print the insecure-file block;
90
+ * - `unrevocable`: a 404 — the server has no revoke route, so the row ends (retrying is pointless) but the credential may still be live.
91
+ * It is NOT "revoked": the caller prints the orphan wording, which says where to remove it ("0.8.0 truth fixes" F7).
92
+ */
93
+ export interface HeldRevoke {
94
+ readonly kind: "revoked" | "waiting" | "orphan" | "unrevocable";
95
+ readonly insecure: Extract<SecretFileRead, {
96
+ kind: "insecure-file";
97
+ }> | null;
98
+ }
99
+ /**
100
+ * `g`'s every no-grant exit: one revoke within 3 s. With a readable row it goes to the row's recorded origin and removes the row on a
101
+ * 2xx, 401 or 404 (a failure gives the lease up so the next verb retries). With NO usable row — missing, or the queue refused — it
102
+ * revokes with the in-memory credential at the MINTED origin: a missing row is never taken for "already revoked".
103
+ */
104
+ export declare function revokeHeld(home: string, credential: IngestCredential, mintedOrigin: string, fetchImpl: typeof fetch): Promise<HeldRevoke>;
105
+ /**
106
+ * Every user-facing verb, once, at its start: one attempt at each row, and ONE 3 s bound across all of them (a row gets what is
107
+ * left of the bound, and none is attempted once it is spent). Silent, and needs no credential.
108
+ *
109
+ * Before EACH request it re-reads the queue and the current credential (a credential committed, or a row settled, after the loop
110
+ * began is never revoked on a stale view), and it
111
+ * - skips and DROPS a row whose token is the current credential (the user kept it; the kill came between the credential's write
112
+ * and the row's deletion) — compared on the file's raw token, so a mode problem does not hide a kept credential;
113
+ * - skips (and keeps) a LEASED row: `g` is holding it at its y/N in another process;
114
+ * - drops a row past `minted_at` + 91 days.
115
+ * Removes each row it settles as it goes, re-reading just before it writes. Returns how many rows still wait. Never throws.
116
+ */
117
+ export declare function retryPendingRevokes(home: string, env: NodeJS.ProcessEnv, fetchImpl: typeof fetch, now: Date, deadlineMs?: number): Promise<number>;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Pending revokes (slice 2, S4, R3-11): the WRITE-AHEAD record of a credential `g` has made and not yet been told to keep.
3
+ *
4
+ * ```
5
+ * addPendingRevoke(home, credential, endpoint, now) at the mint, BEFORE anything else: refuses (never overwrites) when it cannot
6
+ * removePendingRevoke(home, token) after the revoke, or after the credential is kept: re-reads, removes ONE row
7
+ * revokeHeld(home, token, fetch) one best-effort revoke (3 s) of a row, to ITS recorded origin
8
+ * retryPendingRevokes(home, env, fetch, now) every user-facing verb, once, at its start: one 3 s bound across all rows
9
+ * readPendingRevokes(home) how many are waiting (for `status`'s `pending` count)
10
+ * ```
11
+ *
12
+ * ## ⛔ WHY IT IS WRITE-AHEAD
13
+ *
14
+ * `g` is the one place a credential is made ahead of consent (exception 3). So the credential lives in MEMORY until the yes:
15
+ * `credentials.json` does not exist before it, and the row below is written the instant the server hands the token over. A kill, a
16
+ * second Ctrl+C, a SIGHUP at the y/N or an unexpected throw therefore leaves a ROW (which the next verb revokes), never a live
17
+ * credential on disk that nothing remembers. The yes commits in this order: the grants, then `credentials.json`, then the row's
18
+ * deletion; a retry skips and drops any row whose token IS the current credential, so a kill between the last two never revokes what
19
+ * the user kept.
20
+ *
21
+ * ## ⛔ THE FILE HOLDS A BEARER TOKEN — the same exposure class as `credentials.json`, held to the same rules
22
+ * - written mode 0600 under the 0700 directory, temp + rename;
23
+ * - READ through `readSecretFile`: a file or folder others can read is refused, and an insecure or unreadable file is NEVER
24
+ * overwritten (the writer refuses, and `g` fails closed);
25
+ * - never printed, logged or returned: `status` counts the rows and shows nothing else, `--json` included;
26
+ * - a hook never touches it (only the CLI verbs import this module; `test/connect-account.test.ts` walks the hooks' import graph).
27
+ *
28
+ * ## ⛔ A ROW IS NEVER DROPPED UNREVOKED, except by the clock
29
+ * - it leaves on a 2xx, a 401 (the server does not accept it: already dead) or a 404 (a server that cannot revoke);
30
+ * - or at `minted_at` + 91 days: a token that was never on disk was never used, and an ingest credential ends 90 days unused;
31
+ * - there is no row cap that drops one: at the cap `addPendingRevoke` REFUSES, and `g` fails closed.
32
+ *
33
+ * ## ⛔ THE ORIGIN IS THE ROW'S
34
+ *
35
+ * A row records the issuing server's origin and a retry goes ONLY there (https, or http on loopback: `isAllowedIngestUrl`), never to
36
+ * wherever `VIBECOMMIT_MCP_URL` points today.
37
+ */
38
+ import { chmodSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
39
+ import { writeFileAtomic } from "./atomic_write.js";
40
+ import { IngestCredential, INGEST_TOKEN_PREFIX } from "./credential.js";
41
+ import { LEASE_MS, leased, readPendingRevokes, readQueue } from "./pending_revoke_file.js";
42
+ import { REVOKE_TIMEOUT_MS, selfRevokeOutcome } from "./oauth/retire.js";
43
+ import { credentialsPath, pendingRevokePath, rootDir } from "./paths.js";
44
+ import { isAllowedIngestUrl } from "./post.js";
45
+ export { readPendingRevokes } from "./pending_revoke_file.js";
46
+ /** More rows than `g` can produce in earnest: past it `g` refuses rather than drop one. */
47
+ export const MAX_ROWS = 8;
48
+ /** A token never used ends 90 days after it is made; one day of slack. */
49
+ const MAX_AGE_MS = 91 * 24 * 60 * 60 * 1000;
50
+ /** One best-effort revoke, and the bound on a verb's whole retry. */
51
+ export { REVOKE_TIMEOUT_MS };
52
+ function writeRows(home, rows) {
53
+ if (rows.length === 0) {
54
+ try {
55
+ unlinkSync(pendingRevokePath(home));
56
+ }
57
+ catch {
58
+ /* already gone */
59
+ }
60
+ return;
61
+ }
62
+ mkdirSync(rootDir(home), { recursive: true, mode: 0o700 });
63
+ chmodSync(rootDir(home), 0o700);
64
+ writeFileAtomic(pendingRevokePath(home), `${JSON.stringify({ revokes: rows }, null, 2)}\n`, 0o600);
65
+ }
66
+ /**
67
+ * Write the row for a credential that has just been made. Never overwrites a file it cannot read, never drops a row.
68
+ * `lease: true` is `g` holding the credential at its y/N: the row carries `{ pid, lease_until }` (30 minutes) so another verb's retry
69
+ * leaves it alone while it is kept; every other writer (a test, an orphaned row) has no lease and is retried at once.
70
+ */
71
+ export function addPendingRevoke(home, credential, endpoint, now, options = {}) {
72
+ try {
73
+ const token = credential.expose();
74
+ if (!token.startsWith(INGEST_TOKEN_PREFIX))
75
+ return { kind: "failed" };
76
+ const queue = readQueue(home);
77
+ if (queue.kind === "refused")
78
+ return { kind: "refused", read: queue.read };
79
+ const rows = queue.kind === "ok" ? queue.rows : [];
80
+ if (rows.some((row) => row.token === token))
81
+ return { kind: "written" };
82
+ if (rows.length >= MAX_ROWS)
83
+ return { kind: "full" };
84
+ const lease = options.lease === true ? { pid: process.pid, lease_until: new Date(now.getTime() + LEASE_MS).toISOString() } : null;
85
+ writeRows(home, [...rows, { token, minted_at: now.toISOString(), origin: new URL(endpoint).origin, lease }]);
86
+ return { kind: "written" };
87
+ }
88
+ catch {
89
+ return { kind: "failed" };
90
+ }
91
+ }
92
+ /** Remove ONE row, re-reading just before the write so a row queued in between survives. Deletes the file when it is empty. Never throws. */
93
+ export function removePendingRevoke(home, token) {
94
+ removeRows(home, new Set([token]));
95
+ }
96
+ /** Give the row up to the retry: `g` is no longer holding the credential, so the next verb may revoke it. Re-reads before it writes. */
97
+ export function releaseLease(home, token) {
98
+ try {
99
+ const queue = readQueue(home);
100
+ if (queue.kind !== "ok" || !queue.rows.some((row) => row.token === token && row.lease !== null))
101
+ return;
102
+ writeRows(home, queue.rows.map((row) => (row.token === token ? { ...row, lease: null } : row)));
103
+ }
104
+ catch {
105
+ /* the lease runs out by itself */
106
+ }
107
+ }
108
+ /**
109
+ * Is the row for `token` still there? `g` asks just before it commits, and the three answers are different things:
110
+ * - `held`: the row is in a queue this client can use;
111
+ * - `gone`: the queue is readable and the row is not in it — another verb settled (revoked) it;
112
+ * - `unusable`: the queue is insecure or unreadable. Nobody revoked anything; this client can neither keep nor settle the record.
113
+ */
114
+ export function holdsRow(home, token) {
115
+ const queue = readQueue(home);
116
+ if (queue.kind === "refused")
117
+ return "unusable";
118
+ return queue.kind === "ok" && queue.rows.some((row) => row.token === token) ? "held" : "gone";
119
+ }
120
+ function removeRows(home, tokens) {
121
+ try {
122
+ const queue = readQueue(home);
123
+ if (queue.kind !== "ok")
124
+ return;
125
+ const rows = queue.rows.filter((row) => !tokens.has(row.token));
126
+ if (rows.length !== queue.rows.length)
127
+ writeRows(home, rows);
128
+ }
129
+ catch {
130
+ /* the row stays; the next verb retries */
131
+ }
132
+ }
133
+ /** Revoke a token, with itself as the bearer, at `origin` — and only where a credential may be sent at all. */
134
+ async function revokeToken(origin, token, fetchImpl, timeoutMs) {
135
+ if (!isAllowedIngestUrl(origin))
136
+ return null;
137
+ const outcome = await selfRevokeOutcome(origin, new IngestCredential(token, "file"), { fetch: fetchImpl, timeoutMs });
138
+ return outcome.kind === "retired" || outcome.kind === "gone" ? "revoked" : outcome.kind === "unknown" ? "unrevocable" : null;
139
+ }
140
+ /** The tokens this machine is using right now: `credentials.json`'s, read RAW (a mode problem does not hide it) and the environment's. */
141
+ function currentTokens(home, env) {
142
+ const out = new Set();
143
+ try {
144
+ const token = JSON.parse(readFileSync(credentialsPath(home), "utf8")).token;
145
+ if (typeof token === "string" && token !== "")
146
+ out.add(token);
147
+ }
148
+ catch {
149
+ /* no file, or not ours: nothing to protect */
150
+ }
151
+ const envToken = env.VIBECOMMIT_TOKEN?.trim();
152
+ if (envToken !== undefined && envToken !== "")
153
+ out.add(envToken);
154
+ return out;
155
+ }
156
+ /**
157
+ * `g`'s every no-grant exit: one revoke within 3 s. With a readable row it goes to the row's recorded origin and removes the row on a
158
+ * 2xx, 401 or 404 (a failure gives the lease up so the next verb retries). With NO usable row — missing, or the queue refused — it
159
+ * revokes with the in-memory credential at the MINTED origin: a missing row is never taken for "already revoked".
160
+ */
161
+ export async function revokeHeld(home, credential, mintedOrigin, fetchImpl) {
162
+ const token = credential.expose();
163
+ let insecure = null;
164
+ try {
165
+ const queue = readQueue(home);
166
+ const row = queue.kind === "ok" ? queue.rows.find((r) => r.token === token) : undefined;
167
+ if (queue.kind === "refused" && queue.read.kind === "insecure-file")
168
+ insecure = queue.read;
169
+ if (row !== undefined) {
170
+ const ended = await revokeToken(row.origin, token, fetchImpl, REVOKE_TIMEOUT_MS);
171
+ if (ended !== null) {
172
+ removePendingRevoke(home, token);
173
+ return { kind: ended, insecure };
174
+ }
175
+ releaseLease(home, token);
176
+ return { kind: "waiting", insecure };
177
+ }
178
+ const ended = await revokeToken(mintedOrigin, token, fetchImpl, REVOKE_TIMEOUT_MS);
179
+ return { kind: ended ?? "orphan", insecure };
180
+ }
181
+ catch {
182
+ return { kind: "orphan", insecure };
183
+ }
184
+ }
185
+ /**
186
+ * Every user-facing verb, once, at its start: one attempt at each row, and ONE 3 s bound across all of them (a row gets what is
187
+ * left of the bound, and none is attempted once it is spent). Silent, and needs no credential.
188
+ *
189
+ * Before EACH request it re-reads the queue and the current credential (a credential committed, or a row settled, after the loop
190
+ * began is never revoked on a stale view), and it
191
+ * - skips and DROPS a row whose token is the current credential (the user kept it; the kill came between the credential's write
192
+ * and the row's deletion) — compared on the file's raw token, so a mode problem does not hide a kept credential;
193
+ * - skips (and keeps) a LEASED row: `g` is holding it at its y/N in another process;
194
+ * - drops a row past `minted_at` + 91 days.
195
+ * Removes each row it settles as it goes, re-reading just before it writes. Returns how many rows still wait. Never throws.
196
+ */
197
+ export async function retryPendingRevokes(home, env, fetchImpl, now, deadlineMs = REVOKE_TIMEOUT_MS) {
198
+ try {
199
+ const first = readQueue(home);
200
+ if (first.kind !== "ok")
201
+ return 0;
202
+ const started = Date.now();
203
+ for (const listed of first.rows) {
204
+ const fresh = readQueue(home);
205
+ if (fresh.kind !== "ok")
206
+ break;
207
+ const row = fresh.rows.find((r) => r.token === listed.token);
208
+ if (row === undefined)
209
+ continue;
210
+ if (currentTokens(home, env).has(row.token)) {
211
+ removePendingRevoke(home, row.token);
212
+ continue;
213
+ }
214
+ if (leased(row, now))
215
+ continue;
216
+ const age = now.getTime() - Date.parse(row.minted_at);
217
+ if (Number.isFinite(age) && age > MAX_AGE_MS) {
218
+ removePendingRevoke(home, row.token);
219
+ continue;
220
+ }
221
+ const left = deadlineMs - (Date.now() - started);
222
+ if (left <= 0)
223
+ break;
224
+ if ((await revokeToken(row.origin, row.token, fetchImpl, left)) !== null)
225
+ removePendingRevoke(home, row.token);
226
+ }
227
+ return readPendingRevokes(home).length;
228
+ }
229
+ catch {
230
+ return readPendingRevokes(home).length;
231
+ }
232
+ }
233
+ //# sourceMappingURL=pending_revoke.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending_revoke.js","sourceRoot":"","sources":["../src/pending_revoke.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The pending-revoke queue's FILE: its path's format, and reading it (slice 2, S4). Split from `pending_revoke.ts` so that what
3
+ * `status` needs — a COUNT — does not drag the writer and the network revoke onto every process's load path.
4
+ *
5
+ * Read through `readSecretFile` (the same mode checks as `credentials.json`): a file or folder others can read, a file that is not
6
+ * what this client wrote, or one with a row it cannot read all come back `refused`, and `pending_revoke.ts` never overwrites those.
7
+ * Nothing here writes, sends or prints a token; the only thing it hands `status` is how many rows wait.
8
+ */
9
+ import { type SecretFileRead } from "./credential.js";
10
+ export interface PendingRevoke {
11
+ readonly token: string;
12
+ /** ISO 8601, when the server made the credential. */
13
+ readonly minted_at: string;
14
+ /** The issuing server's origin (`https://api.vibecommit.ai`): the only place a retry sends this token. */
15
+ readonly origin: string;
16
+ /**
17
+ * While `g` is holding the credential (waiting at its y/N) the row carries the holder's lease: a retry run by ANOTHER verb in the
18
+ * meantime must not revoke a credential that is about to be kept. A row with no lease is an orphan. See `leased`.
19
+ */
20
+ readonly lease: {
21
+ readonly pid: number;
22
+ readonly lease_until: string;
23
+ } | null;
24
+ }
25
+ /** How long a holder's lease lasts: a `g` whose question stays open longer may be revoked by another verb, and its commit then refuses. */
26
+ export declare const LEASE_MS: number;
27
+ /**
28
+ * Is the row leased at `now`? BOTH must hold: the holder's pid is alive (`kill(pid, 0)`: EPERM counts as alive, ESRCH as dead) and
29
+ * `now` is before the EARLIER of the row's own `lease_until` and `minted_at` + 30 minutes. The lease is capped at its mint: a
30
+ * `lease_until` written far in the future (a bug, or an edit) does not keep a live pid's row out of the retry for ever. A dead holder or
31
+ * an expired lease is an orphan, which any verb's retry revokes.
32
+ */
33
+ export declare function leased(row: PendingRevoke, now: Date): boolean;
34
+ export type Queue = {
35
+ readonly kind: "ok";
36
+ readonly rows: PendingRevoke[];
37
+ } | {
38
+ readonly kind: "absent";
39
+ }
40
+ /** Present but not safe to read or write: never read, never overwritten. */
41
+ | {
42
+ readonly kind: "refused";
43
+ readonly read: Exclude<SecretFileRead, {
44
+ kind: "ok";
45
+ } | {
46
+ kind: "absent";
47
+ }> | {
48
+ readonly kind: "unreadable";
49
+ };
50
+ };
51
+ export declare function readQueue(home: string): Queue;
52
+ /** The rows waiting, for a count. An absent, insecure or unreadable file reads as none (it is never sent from). */
53
+ export declare function readPendingRevokes(home: string): PendingRevoke[];
54
+ /** How many rows wait: all `status` shows of the queue. */
55
+ export declare function countPendingRevokes(home: string): number;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The pending-revoke queue's FILE: its path's format, and reading it (slice 2, S4). Split from `pending_revoke.ts` so that what
3
+ * `status` needs — a COUNT — does not drag the writer and the network revoke onto every process's load path.
4
+ *
5
+ * Read through `readSecretFile` (the same mode checks as `credentials.json`): a file or folder others can read, a file that is not
6
+ * what this client wrote, or one with a row it cannot read all come back `refused`, and `pending_revoke.ts` never overwrites those.
7
+ * Nothing here writes, sends or prints a token; the only thing it hands `status` is how many rows wait.
8
+ */
9
+ import { INGEST_TOKEN_PREFIX, readSecretFile } from "./credential.js";
10
+ import { pendingRevokePath } from "./paths.js";
11
+ /** How long a holder's lease lasts: a `g` whose question stays open longer may be revoked by another verb, and its commit then refuses. */
12
+ export const LEASE_MS = 30 * 60 * 1000;
13
+ /**
14
+ * Is the row leased at `now`? BOTH must hold: the holder's pid is alive (`kill(pid, 0)`: EPERM counts as alive, ESRCH as dead) and
15
+ * `now` is before the EARLIER of the row's own `lease_until` and `minted_at` + 30 minutes. The lease is capped at its mint: a
16
+ * `lease_until` written far in the future (a bug, or an edit) does not keep a live pid's row out of the retry for ever. A dead holder or
17
+ * an expired lease is an orphan, which any verb's retry revokes.
18
+ */
19
+ export function leased(row, now) {
20
+ if (row.lease === null)
21
+ return false;
22
+ const until = Math.min(Date.parse(row.lease.lease_until), Date.parse(row.minted_at) + LEASE_MS);
23
+ if (!(until > now.getTime()))
24
+ return false;
25
+ try {
26
+ process.kill(row.lease.pid, 0);
27
+ return true;
28
+ }
29
+ catch (error) {
30
+ return error.code === "EPERM";
31
+ }
32
+ }
33
+ export function readQueue(home) {
34
+ const read = readSecretFile(home, pendingRevokePath(home));
35
+ if (read.kind === "absent")
36
+ return { kind: "absent" };
37
+ if (read.kind !== "ok")
38
+ return { kind: "refused", read };
39
+ try {
40
+ const doc = JSON.parse(read.raw);
41
+ if (doc === null || typeof doc !== "object" || !Array.isArray(doc.revokes))
42
+ return { kind: "refused", read: { kind: "unreadable" } };
43
+ const rows = [];
44
+ for (const row of doc.revokes) {
45
+ if (row === null || typeof row !== "object")
46
+ return { kind: "refused", read: { kind: "unreadable" } };
47
+ const r = row;
48
+ if (typeof r.token !== "string" || !r.token.startsWith(INGEST_TOKEN_PREFIX) || typeof r.minted_at !== "string" || typeof r.origin !== "string") {
49
+ // A row this client cannot read is a row it must not lose: refuse the whole file rather than rewrite it without the row.
50
+ return { kind: "refused", read: { kind: "unreadable" } };
51
+ }
52
+ // A lease this client cannot read is NO lease: the row is an orphan, which errs toward revoking.
53
+ const l = r.lease;
54
+ const lease = l !== null && l !== undefined && typeof l === "object" && typeof l.pid === "number" && Number.isInteger(l.pid) && typeof l.lease_until === "string"
55
+ ? { pid: l.pid, lease_until: l.lease_until }
56
+ : null;
57
+ rows.push({ token: r.token, minted_at: r.minted_at, origin: r.origin, lease });
58
+ }
59
+ return { kind: "ok", rows };
60
+ }
61
+ catch {
62
+ return { kind: "refused", read: { kind: "unreadable" } };
63
+ }
64
+ }
65
+ /** The rows waiting, for a count. An absent, insecure or unreadable file reads as none (it is never sent from). */
66
+ export function readPendingRevokes(home) {
67
+ const queue = readQueue(home);
68
+ return queue.kind === "ok" ? queue.rows : [];
69
+ }
70
+ /** How many rows wait: all `status` shows of the queue. */
71
+ export function countPendingRevokes(home) {
72
+ return readPendingRevokes(home).length;
73
+ }
74
+ //# sourceMappingURL=pending_revoke_file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending_revoke_file.js","sourceRoot":"","sources":["../src/pending_revoke_file.ts"],"names":[],"mappings":"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"}