@bragi-gmbh/codebus 1.2.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 (133) hide show
  1. package/bin/codebus.js +2 -0
  2. package/dist/binding-guard.js +97 -0
  3. package/dist/binding-guard.js.map +1 -0
  4. package/dist/client.js +94 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/commands/ack.js +47 -0
  7. package/dist/commands/ack.js.map +1 -0
  8. package/dist/commands/archive.js +21 -0
  9. package/dist/commands/archive.js.map +1 -0
  10. package/dist/commands/as.js +299 -0
  11. package/dist/commands/as.js.map +1 -0
  12. package/dist/commands/ask.js +189 -0
  13. package/dist/commands/ask.js.map +1 -0
  14. package/dist/commands/auth.js +33 -0
  15. package/dist/commands/auth.js.map +1 -0
  16. package/dist/commands/checkin.js +235 -0
  17. package/dist/commands/checkin.js.map +1 -0
  18. package/dist/commands/decision.js +52 -0
  19. package/dist/commands/decision.js.map +1 -0
  20. package/dist/commands/digest.js +117 -0
  21. package/dist/commands/digest.js.map +1 -0
  22. package/dist/commands/doctor.js +106 -0
  23. package/dist/commands/doctor.js.map +1 -0
  24. package/dist/commands/factory.js +191 -0
  25. package/dist/commands/factory.js.map +1 -0
  26. package/dist/commands/github-login.js +205 -0
  27. package/dist/commands/github-login.js.map +1 -0
  28. package/dist/commands/graph.js +88 -0
  29. package/dist/commands/graph.js.map +1 -0
  30. package/dist/commands/health.js +34 -0
  31. package/dist/commands/health.js.map +1 -0
  32. package/dist/commands/inbox.js +83 -0
  33. package/dist/commands/inbox.js.map +1 -0
  34. package/dist/commands/ingest.js +553 -0
  35. package/dist/commands/ingest.js.map +1 -0
  36. package/dist/commands/init.js +97 -0
  37. package/dist/commands/init.js.map +1 -0
  38. package/dist/commands/install.js +520 -0
  39. package/dist/commands/install.js.map +1 -0
  40. package/dist/commands/invite.js +152 -0
  41. package/dist/commands/invite.js.map +1 -0
  42. package/dist/commands/jira.js +237 -0
  43. package/dist/commands/jira.js.map +1 -0
  44. package/dist/commands/join.js +96 -0
  45. package/dist/commands/join.js.map +1 -0
  46. package/dist/commands/links.js +66 -0
  47. package/dist/commands/links.js.map +1 -0
  48. package/dist/commands/login.js +84 -0
  49. package/dist/commands/login.js.map +1 -0
  50. package/dist/commands/morning.js +121 -0
  51. package/dist/commands/morning.js.map +1 -0
  52. package/dist/commands/next.js +66 -0
  53. package/dist/commands/next.js.map +1 -0
  54. package/dist/commands/onboarding.js +109 -0
  55. package/dist/commands/onboarding.js.map +1 -0
  56. package/dist/commands/pack.js +276 -0
  57. package/dist/commands/pack.js.map +1 -0
  58. package/dist/commands/plan-gh.js +425 -0
  59. package/dist/commands/plan-gh.js.map +1 -0
  60. package/dist/commands/plan.js +465 -0
  61. package/dist/commands/plan.js.map +1 -0
  62. package/dist/commands/pr.js +88 -0
  63. package/dist/commands/pr.js.map +1 -0
  64. package/dist/commands/question.js +142 -0
  65. package/dist/commands/question.js.map +1 -0
  66. package/dist/commands/read.js +84 -0
  67. package/dist/commands/read.js.map +1 -0
  68. package/dist/commands/refs.js +357 -0
  69. package/dist/commands/refs.js.map +1 -0
  70. package/dist/commands/reply.js +38 -0
  71. package/dist/commands/reply.js.map +1 -0
  72. package/dist/commands/repo.js +1198 -0
  73. package/dist/commands/repo.js.map +1 -0
  74. package/dist/commands/report.js +41 -0
  75. package/dist/commands/report.js.map +1 -0
  76. package/dist/commands/resolve-refs.js +21 -0
  77. package/dist/commands/resolve-refs.js.map +1 -0
  78. package/dist/commands/resolve.js +93 -0
  79. package/dist/commands/resolve.js.map +1 -0
  80. package/dist/commands/send.js +168 -0
  81. package/dist/commands/send.js.map +1 -0
  82. package/dist/commands/sent.js +22 -0
  83. package/dist/commands/sent.js.map +1 -0
  84. package/dist/commands/session.js +426 -0
  85. package/dist/commands/session.js.map +1 -0
  86. package/dist/commands/statusline.js +43 -0
  87. package/dist/commands/statusline.js.map +1 -0
  88. package/dist/commands/task.js +140 -0
  89. package/dist/commands/task.js.map +1 -0
  90. package/dist/commands/tick.js +233 -0
  91. package/dist/commands/tick.js.map +1 -0
  92. package/dist/commands/upgrade.js +76 -0
  93. package/dist/commands/upgrade.js.map +1 -0
  94. package/dist/commands/watch.js +169 -0
  95. package/dist/commands/watch.js.map +1 -0
  96. package/dist/commands/whoami.js +27 -0
  97. package/dist/commands/whoami.js.map +1 -0
  98. package/dist/config.js +103 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/directives.js +102 -0
  101. package/dist/directives.js.map +1 -0
  102. package/dist/errors.js +110 -0
  103. package/dist/errors.js.map +1 -0
  104. package/dist/git-hooks.js +116 -0
  105. package/dist/git-hooks.js.map +1 -0
  106. package/dist/graph-bootstrap.js +95 -0
  107. package/dist/graph-bootstrap.js.map +1 -0
  108. package/dist/graph-remote.js +889 -0
  109. package/dist/graph-remote.js.map +1 -0
  110. package/dist/graph.js +335 -0
  111. package/dist/graph.js.map +1 -0
  112. package/dist/index.js +154 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/kit/graph-publish.mjs +400 -0
  115. package/dist/mail-latency.js +120 -0
  116. package/dist/mail-latency.js.map +1 -0
  117. package/dist/mcp-registry.js +859 -0
  118. package/dist/mcp-registry.js.map +1 -0
  119. package/dist/mcp.js +193 -0
  120. package/dist/mcp.js.map +1 -0
  121. package/dist/output.js +12 -0
  122. package/dist/output.js.map +1 -0
  123. package/dist/positioning.js +21 -0
  124. package/dist/positioning.js.map +1 -0
  125. package/dist/rolestore.js +89 -0
  126. package/dist/rolestore.js.map +1 -0
  127. package/dist/session.js +49 -0
  128. package/dist/session.js.map +1 -0
  129. package/dist/sessionstore.js +736 -0
  130. package/dist/sessionstore.js.map +1 -0
  131. package/dist/version-check.js +97 -0
  132. package/dist/version-check.js.map +1 -0
  133. package/package.json +47 -0
@@ -0,0 +1,425 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { resolveConfig } from "../config.js";
4
+ import { makeCtx } from "../client.js";
5
+ import { render } from "../output.js";
6
+ import { CodebusError } from "../errors.js";
7
+ import { sendMessageCore } from "./send.js";
8
+ import { ACT_DIRECTIVE_CORE } from "../directives.js";
9
+ const execFileP = promisify(execFile);
10
+ /**
11
+ * GitHub bridge (spec §A.2.5) — the marker-keyed issue flow.
12
+ *
13
+ * Round-1 review (CB-MSG-005365) BLOCKER 1/3 fix: gh-ensure no longer
14
+ * trusts caller-supplied --ticket/--repo as identity. It first calls
15
+ * `plan_gh_reserve(dispatch_id)` (supabase/migrations/0056_plan_gh_
16
+ * reserve_and_view_invoker.sql) — authenticated exactly like plan_gh_record
17
+ * (caller must be the dispatch's CURRENT recipient) — which returns the
18
+ * SERVER's own ticket_id/repo binding for that dispatch and takes a short
19
+ * per-ticket LEASE (closing the redelivery race where two concurrent
20
+ * gh-ensure runs could both see zero marker matches and both create a
21
+ * duplicate issue — BLOCKER 3). Caller-supplied --ticket/--repo, if given,
22
+ * are cross-checked against this server truth and REJECTED on mismatch —
23
+ * BEFORE any `gh` call — rather than trusted outright; if omitted, the
24
+ * server values are used directly. This RPC exists because 0049's RLS
25
+ * (`plan_visible`: owner/planner/workspace-admin only) gives the dispatch
26
+ * RECIPIENT itself no read access to plan_tickets/plan_ticket_dispatches —
27
+ * a plain SELECT as the worker returns zero rows — so a SECURITY DEFINER
28
+ * RPC authenticated on dispatch-recipient identity is the only path.
29
+ *
30
+ * gh-ensure never calls the real GitHub API in tests — the GhRunner
31
+ * interface is injected (default: realGhRunner(), which shells out to the
32
+ * `gh` binary); tests pass a mock recording invocations.
33
+ */
34
+ export function planTicketMarker(ticketId) {
35
+ // HTML-comment wrapped, exact line, keyed by TICKET id (not dispatch id)
36
+ // per §A.2.5 — a re-dispatch after failure must still find the FIRST
37
+ // attempt's issue.
38
+ return `<!-- codebus-plan-ticket: ${ticketId} -->`;
39
+ }
40
+ /**
41
+ * Parses a github.com remote_url into 'owner/repo' (lowercased). Originally
42
+ * written to mirror 0049's plan_repo_from_remote_url SQL function —
43
+ * reimplemented here in TS rather than called via RPC, since that helper
44
+ * carries no explicit grant to `authenticated` (it's an internal helper
45
+ * invoked only from inside other SECURITY DEFINER functions; the CLI runs
46
+ * as an ordinary authenticated agent, never the migration-owning role).
47
+ *
48
+ * Round-16 review finding (CONFIRMED, MAJOR): that SQL function's own regex
49
+ * — and the ORIGINAL version of this one, which faithfully mirrored it — is
50
+ * UNANCHORED: it matches the literal substring `github.com` followed by
51
+ * `:` or `/` ANYWHERE in the input, not only at the start of the host
52
+ * component. `https://notgithub.com/acme/widgets` contains that exact
53
+ * substring (`notGITHUB.COM/acme/widgets`) and parsed to `acme/widgets` —
54
+ * identical to what the GENUINE `https://github.com/acme/widgets` parses
55
+ * to. 0070's own `repos_enforce_remote_url_normalized` trigger already
56
+ * closed this exact class of bug for `repos.remote_url` on the WRITE path
57
+ * (round-9 review finding 7a, via its own separate anchored host check
58
+ * BEFORE calling the SQL function) — but `ingest.ts`'s automatic repo
59
+ * resolution (resolveConnectedRepo) calls this function on the READ path
60
+ * against an ENTIRELY UNTRUSTED value: the local checkout's own `git
61
+ * remote get-url origin`, which no trigger ever validates. A checkout
62
+ * whose origin points at a lookalike host (`notgithub.com`, or
63
+ * `github.com.evil.com` — a subdomain-suffix trick, also unanchored-substring-
64
+ * matched by the old regex) would parse to the SAME `owner/repo` string as
65
+ * the genuine GitHub repo already connected in this workspace, and
66
+ * `ingest.ts`'s own equality match (`parseOwnerRepo(r.remote_url) ===
67
+ * ownerRepo`) would then attribute that checkout's local content to the
68
+ * genuine repo's graph record.
69
+ *
70
+ * Fixed by anchoring the host component precisely — mirroring the SAME
71
+ * technique repo.ts's own `parseGithubUrl` already uses correctly (verified:
72
+ * neither `notgithub.com` nor `github.com.evil.com` nor `gist.github.com`
73
+ * matches that function's anchored pattern either) — rather than a bare
74
+ * substring search: the literal `github.com` must appear starting
75
+ * IMMEDIATELY after the scheme (and optional userinfo `user@`) for the
76
+ * https/ssh forms, or immediately after `git@` for the scp-style form, with
77
+ * nothing else (no leading characters, no trailing characters before the
78
+ * required `:`/`/` delimiter) allowed to precede or extend it. Case-
79
+ * insensitive (matches GitHub's own case-insensitive host semantics).
80
+ *
81
+ * Audited every caller of this shared parser before fixing it here directly
82
+ * (rather than adding a separate anchored variant): both existing callers —
83
+ * this file's own `ghEnsureCore` (line ~196, parsing a DB-stored
84
+ * `repos.remote_url` that 0070's trigger already anchors at write time —
85
+ * unaffected by this fix, since a value that already passed that trigger's
86
+ * check is, by construction, an anchored github.com URL either way) and
87
+ * `ingest.ts`'s `resolveConnectedRepo` (the vulnerable read-path call this
88
+ * finding is about) — already assumed GitHub-only semantics; the pre-
89
+ * existing test suite (`plan-gh.test.ts`) already asserted a non-github.com
90
+ * host (gitlab.com) returns null, so no caller relies on this function
91
+ * accepting anything this fix newly rejects. No `graph-remote` file exists
92
+ * in this codebase to audit (checked). Deliberately does NOT add bare
93
+ * `owner/repo` (no-host) acceptance the way repo.ts's `parseGithubUrl`
94
+ * does — neither caller here ever passes a bare string (ingest.ts's input
95
+ * is always a real `git remote get-url` result; plan-gh.ts's is always a
96
+ * `repos.remote_url` already constrained to a full URL by 0070's trigger),
97
+ * so adding it now would be unrelated, untested scope creep, not a fix.
98
+ */
99
+ export function parseOwnerRepo(remoteUrl) {
100
+ const s = remoteUrl.trim();
101
+ const tail = "([A-Za-z0-9._-]+)/([A-Za-z0-9._-]+?)(?:\\.git)?/?$";
102
+ const patterns = [
103
+ new RegExp(`^https?://(?:[^/@\\s]+@)?github\\.com(?::\\d+)?/${tail}`, "i"),
104
+ new RegExp(`^git@github\\.com:${tail}`, "i"),
105
+ new RegExp(`^ssh://git@github\\.com(?::\\d+)?/${tail}`, "i"),
106
+ ];
107
+ for (const re of patterns) {
108
+ const m = s.match(re);
109
+ if (m)
110
+ return `${m[1]}/${m[2].replace(/\.git$/i, "")}`.toLowerCase();
111
+ }
112
+ return null;
113
+ }
114
+ /**
115
+ * Round-19 review (Luna PR #55 review, 3 CONFIRMED findings — comments
116
+ * 3826839023/3826839028/3826839029 — all one theme, credential leakage via
117
+ * remote URLs): a git remote URL can legally embed HTTP Basic-style
118
+ * userinfo (`https://x-access-token:ghp_xxx@github.com/owner/repo` is a
119
+ * normal way to authenticate a clone). `parseOwnerRepo` above already
120
+ * discards that segment in its own return value (it is inside the
121
+ * non-capturing `(?:[^/@\s]+@)?` group, never one of the captured owner/
122
+ * repo groups), so parseOwnerRepo's OUTPUT was never the leak. The leak was
123
+ * always at the CALL SITES: every caller that echoes the RAW input URL back
124
+ * in an error message once parsing fails or a lookup comes up empty
125
+ * (`ingest.ts`'s resolveConnectedRepo, `repo.ts`'s repoConnectCore/
126
+ * validateOriginMatchesConnectedRepo, and this file's own dispatchAct)
127
+ * printed that string verbatim — including any credential it carried — to
128
+ * stderr/CI logs. `hasUrlUserinfo`/`redactUrlUserinfo` are the shared
129
+ * helpers every one of those call sites now uses (this file's own
130
+ * `ghEnsureCore`, below, included — same class of leak, found while
131
+ * sweeping for every other place a remote URL could reach error text);
132
+ * mirrors the identical
133
+ * pair added server-side in `supabase/migrations/0070_repo_connect.sql`
134
+ * (`has_url_userinfo`/`redact_url_userinfo`) — same regex, same contract,
135
+ * kept in the two languages this codebase actually needs it in rather than
136
+ * a single shared module neither side can import from the other.
137
+ */
138
+ export function hasUrlUserinfo(url) {
139
+ // Userinfo lives between the scheme separator and the first '/' — i.e.
140
+ // anywhere in the authority component, never in the path/query/fragment.
141
+ // `[^/]*@` bounded by "no slash before the '@'" is exactly that
142
+ // component, so `https://github.com/user@example` (an '@' in the PATH,
143
+ // after the host) correctly does not match.
144
+ return /^https?:\/\/[^/]*@/.test(url);
145
+ }
146
+ /** No-op (returns `url` unchanged) when there is no userinfo to strip.
147
+ * Otherwise replaces the ENTIRE matched `scheme://...@` prefix with
148
+ * `scheme://[redacted]@`, so the host/path stay visible for debugging
149
+ * while the credential itself never appears in output. MUST be called on
150
+ * any variable holding a raw git remote URL before it appears in a thrown
151
+ * error, a console.log/console.error, or any other human- or log-visible
152
+ * text — never interpolate a raw remote/origin URL directly. */
153
+ export function redactUrlUserinfo(url) {
154
+ return url.replace(/^(https?:\/\/)[^/]*@/, "$1[redacted]@");
155
+ }
156
+ /**
157
+ * Round-1 review (CB-MSG-005365) finding 4/MINOR-3 fix: `filterMarkerMatches`
158
+ * used to check `body.includes(marker)` — a plain substring test, so a
159
+ * lookalike body carrying the marker text with an adversarial PREFIX or
160
+ * SUFFIX on the same line (e.g. `xxx<!-- codebus-plan-ticket: ID -->`, or
161
+ * the marker embedded mid-sentence) would be treated as a match, even
162
+ * though it is not the exact marker LINE the spec requires ("the marker is
163
+ * the ONLY adoption key... an existing issue without the marker — however
164
+ * similar its title — is never adopted", §A.2.5). Fixed: split the body
165
+ * into lines, trim each, and require EXACT equality with the marker on at
166
+ * least one line — a marker that is merely a substring of a longer line no
167
+ * longer counts.
168
+ */
169
+ export function filterMarkerMatches(rows, marker) {
170
+ return rows
171
+ .filter((row) => (row.body ?? "").split(/\r\n|\r|\n/).some((line) => line.trim() === marker))
172
+ .map((row) => ({ number: row.number, state: row.state.toLowerCase(), body: row.body }));
173
+ }
174
+ export function realGhRunner() {
175
+ return {
176
+ /* c8 ignore start -- shells out to the real `gh` binary/GitHub API. The
177
+ * whole point of the GhRunner injection point (see the file header) is
178
+ * that the test suite NEVER exercises this — every ghEnsureCore test
179
+ * passes its own mock explicitly, by design ("gh-ensure never calls the
180
+ * real GitHub API in tests"). Exercising this body for real would mean
181
+ * either shelling to an actually-installed, authenticated `gh` in CI, or
182
+ * hitting github.com — neither is something this suite does. Same class
183
+ * of irreducible external I/O as github-login.ts's DEC-CB-009 EX-001
184
+ * exemption, scoped narrowly to just these two bodies (filterMarkerMatches
185
+ * below, which realGhRunner.search delegates to, IS covered directly). */
186
+ async search(ownerRepo, marker) {
187
+ const { stdout } = await execFileP("gh", [
188
+ "issue", "list", "--repo", ownerRepo, "--state", "all",
189
+ "--search", marker, "--json", "number,state,body", "--limit", "100",
190
+ ]);
191
+ const rows = JSON.parse(stdout);
192
+ return filterMarkerMatches(rows, marker);
193
+ },
194
+ async create(ownerRepo, title, body) {
195
+ const { stdout } = await execFileP("gh", ["issue", "create", "--repo", ownerRepo, "--title", title, "--body", body]);
196
+ const m = stdout.trim().match(/\/issues\/(\d+)\s*$/);
197
+ if (!m)
198
+ throw new CodebusError(`gh-ensure: could not parse an issue number from \`gh issue create\` output: ${stdout.trim()}`);
199
+ return Number(m[1]);
200
+ },
201
+ /* c8 ignore stop */
202
+ };
203
+ }
204
+ async function releaseReservation(ctx, dispatchId) {
205
+ // Best-effort — never let a release failure mask the real error/result.
206
+ try {
207
+ await ctx.supabase.rpc("plan_gh_reserve_release", { p_dispatch_id: dispatchId });
208
+ }
209
+ catch { /* release is an optimization; the lease's own TTL bounds the worst case */ }
210
+ }
211
+ /**
212
+ * The ensure procedure (§A.2.5), now fronted by the server-authoritative
213
+ * reservation (BLOCKER 1/3):
214
+ * 0. plan_gh_reserve(dispatch_id) — server-derived ticket/repo binding +
215
+ * per-ticket lease; caller-supplied --ticket/--repo are cross-checked
216
+ * against it and rejected on mismatch BEFORE any GitHub call.
217
+ * 1. search the repo (open+closed) for the EXACT marker line.
218
+ * 2. exactly one match -> ADOPT (plan_gh_record ... 'adopted').
219
+ * 3. zero matches -> `gh issue create` with the marker in the body, THEN
220
+ * RE-SEARCH (finding 2/MAJOR "re-search-and-reconcile after create"):
221
+ * an external actor (not subject to this process's own per-ticket
222
+ * lease) could still have created a same-marker issue in the window
223
+ * between our search and our create — if the post-create search now
224
+ * finds MORE THAN ONE marked issue, this is treated exactly like an
225
+ * initial multi-match (fail closed, never guess which one is "ours").
226
+ * Otherwise plan_gh_record(..., 'created').
227
+ * 4. multiple matches (initial or post-create) -> do NOT guess:
228
+ * gh_state='failed', owner alert (surfaced later by morning_queue's
229
+ * gh_stalled exception); manual recovery is the owner closing
230
+ * duplicates and re-running ensure.
231
+ * The reservation is released (best-effort) on every exit path so an
232
+ * immediate legitimate re-run is never blocked for the full lease window.
233
+ */
234
+ export async function ghEnsureCore(ctx, opts, runner = realGhRunner()) {
235
+ const { data: reserveRows, error: reserveErr } = await ctx.supabase.rpc("plan_gh_reserve", {
236
+ p_dispatch_id: opts.dispatchId,
237
+ });
238
+ if (reserveErr)
239
+ throw new CodebusError(reserveErr.message);
240
+ const reserved = Array.isArray(reserveRows) ? reserveRows[0] : reserveRows;
241
+ if (!reserved)
242
+ throw new CodebusError(`gh-ensure: dispatch ${opts.dispatchId} did not resolve to a reservable ticket`);
243
+ // Cross-check caller-supplied --ticket/--repo against the SERVER'S OWN
244
+ // binding for this dispatch — BEFORE any GitHub side effect (BLOCKER 1).
245
+ // Caller args are now optional; when supplied they must agree exactly.
246
+ if (opts.ticketId !== undefined && opts.ticketId !== reserved.ticket_id) {
247
+ await releaseReservation(ctx, opts.dispatchId);
248
+ throw new CodebusError(`ticket_mismatch: --ticket ${opts.ticketId} does not match dispatch ${opts.dispatchId}'s ticket (${reserved.ticket_id})`);
249
+ }
250
+ if (opts.repo !== undefined && opts.repo !== reserved.repo_slug) {
251
+ await releaseReservation(ctx, opts.dispatchId);
252
+ throw new CodebusError(`repo_mismatch: --repo ${opts.repo} does not match dispatch ${opts.dispatchId}'s ticket repo (${reserved.repo_slug ?? "(none)"})`);
253
+ }
254
+ if (!reserved.repo_id || !reserved.remote_url) {
255
+ await releaseReservation(ctx, opts.dispatchId);
256
+ throw new CodebusError(`repo_required: ticket ${reserved.ticket_id} has no repo bound`);
257
+ }
258
+ const ticketId = reserved.ticket_id;
259
+ const ownerRepo = parseOwnerRepo(reserved.remote_url);
260
+ if (!ownerRepo) {
261
+ await releaseReservation(ctx, opts.dispatchId);
262
+ // Round-19 review finding (CONFIRMED, credential-leakage theme):
263
+ // reserved.remote_url is a stored repos.remote_url — 0070's own
264
+ // trigger now rejects userinfo at write time, but a legacy row
265
+ // predating that fix could still carry one; never echo it raw.
266
+ throw new CodebusError(`unknown_repo: could not parse owner/repo from remote_url: ${redactUrlUserinfo(reserved.remote_url)}`);
267
+ }
268
+ const title = opts.title ?? reserved.title;
269
+ const marker = planTicketMarker(ticketId);
270
+ try {
271
+ const matches = await runner.search(ownerRepo, marker);
272
+ if (matches.length === 1) {
273
+ const ref = `issue:${ownerRepo}#${matches[0].number}`;
274
+ const { error } = await ctx.supabase.rpc("plan_gh_record", {
275
+ p_dispatch_id: opts.dispatchId, p_ref: ref, p_gh_state: "adopted",
276
+ });
277
+ if (error)
278
+ throw new CodebusError(error.message);
279
+ return { ref, state: "adopted", issueNumber: matches[0].number };
280
+ }
281
+ if (matches.length === 0) {
282
+ const body = `${opts.body ?? ""}\n\n${marker}\n`.trim();
283
+ const issueNumber = await runner.create(ownerRepo, title, body);
284
+ // Re-search-and-reconcile (finding 2/MAJOR): a marker match is keyed
285
+ // by TICKET id, which this process's own reservation lease
286
+ // serializes against OTHER CodeBus callers — but not against an
287
+ // external actor creating a same-marker issue directly on GitHub in
288
+ // the window between our search and our create. Re-checking here
289
+ // catches that residual race rather than blindly trusting our own
290
+ // create() call.
291
+ const postCreateMatches = await runner.search(ownerRepo, marker);
292
+ if (postCreateMatches.length > 1) {
293
+ const { error: failErr } = await ctx.supabase.rpc("plan_gh_record", {
294
+ p_dispatch_id: opts.dispatchId, p_ref: "issue:unresolved/post-create-multi-match#1", p_gh_state: "failed",
295
+ });
296
+ if (failErr)
297
+ throw new CodebusError(failErr.message);
298
+ throw new CodebusError(`gh-ensure: created issue #${issueNumber} in ${ownerRepo}, but a re-search now finds `
299
+ + `${postCreateMatches.length} marked issues for ticket ${ticketId} — a concurrent external actor `
300
+ + "created a duplicate; refusing to guess which is authoritative. Close the duplicates and re-run gh-ensure.");
301
+ }
302
+ const ref = `issue:${ownerRepo}#${issueNumber}`;
303
+ const { error } = await ctx.supabase.rpc("plan_gh_record", {
304
+ p_dispatch_id: opts.dispatchId, p_ref: ref, p_gh_state: "created",
305
+ });
306
+ if (error)
307
+ throw new CodebusError(error.message);
308
+ return { ref, state: "created", issueNumber };
309
+ }
310
+ // Multiple matches (initial search): fail closed rather than guess.
311
+ // plan_gh_record's 'failed' branch ignores p_ref entirely (only
312
+ // 'created'/'adopted' normalize+bind it) — the placeholder value below
313
+ // is never persisted.
314
+ const { error } = await ctx.supabase.rpc("plan_gh_record", {
315
+ p_dispatch_id: opts.dispatchId, p_ref: "issue:unresolved/multi-match#1", p_gh_state: "failed",
316
+ });
317
+ if (error)
318
+ throw new CodebusError(error.message);
319
+ throw new CodebusError(`gh-ensure: ${matches.length} issues in ${ownerRepo} carry the marker for ticket ${ticketId} — `
320
+ + "refusing to guess (ambiguous_marker); close the duplicates and re-run gh-ensure");
321
+ }
322
+ finally {
323
+ await releaseReservation(ctx, opts.dispatchId);
324
+ }
325
+ }
326
+ /**
327
+ * Reconciliation sweep (§A.2.5 lane 3): for dispatches `sent` with
328
+ * `gh_state='pending'` for over `staleSeconds` (default 1h), nudge the
329
+ * recipient to re-run gh-ensure — the keeper never holds a GitHub PAT
330
+ * itself, and this sweep never calls the `gh` binary.
331
+ *
332
+ * Round-1 review (CB-MSG-005365) MINOR finding 6 fix: this used to
333
+ * re-nudge EVERY qualifying dispatch on EVERY run with no cooldown — a
334
+ * periodic sweep would spam the recipient indefinitely while gh_state
335
+ * stayed 'pending'. The claim+cooldown is now atomic server-side
336
+ * (`plan_gh_sweep_claim`, 0056): it marks `gh_last_nudged_at` in the same
337
+ * transaction that selects candidates (`FOR UPDATE SKIP LOCKED`), so
338
+ * concurrent sweep runs cannot double-nudge and a repeat run within
339
+ * `cooldownSeconds` (default 1h) claims nothing for an already-nudged
340
+ * dispatch. plan_visible() scoping (owner/planner/workspace-admin) is
341
+ * re-applied INSIDE the RPC, since a SECURITY DEFINER function's own
342
+ * queries do not otherwise inherit the caller's RLS.
343
+ */
344
+ export async function ghSweepCore(ctx, opts = {}) {
345
+ const { data, error } = await ctx.supabase.rpc("plan_gh_sweep_claim", {
346
+ p_stale_seconds: opts.staleSeconds ?? 3600,
347
+ p_cooldown_seconds: opts.cooldownSeconds ?? 3600,
348
+ });
349
+ if (error)
350
+ throw new CodebusError(error.message);
351
+ const nudged = [];
352
+ for (const row of (data ?? [])) {
353
+ // Round-4 review IMPORTANT finding (comment 3810828378): plan_gh_sweep_claim
354
+ // (0056) already committed `gh_last_nudged_at` for this dispatch in its OWN
355
+ // transaction above — a failure in the sendMessageCore call below (a
356
+ // transient network error, or the recipient having lost workspace
357
+ // membership between claim and send) must not leave that watermark set
358
+ // with no nudge ever delivered, or this dispatch would go silent for the
359
+ // whole cooldown window. Same outbox discipline as everywhere else in
360
+ // this program (0047 fleet_drain, 0057 plan_drain): couple the claim to
361
+ // successful delivery, release on failure, and — mirroring 0057
362
+ // plan_drain's per-row exception isolation — never let one bad row abort
363
+ // nudges already earned by the rest of the batch.
364
+ try {
365
+ await sendMessageCore(ctx, {
366
+ to: row.recipient_slug,
367
+ type: "ask",
368
+ subject: `gh-ensure nudge: dispatch ${row.dispatch_id}`,
369
+ body: `Reconciliation sweep: dispatch ${row.dispatch_id} (ticket ${row.ticket_id}, "${row.title}") has been `
370
+ + `gh_state=pending for a while. Please re-run: codebus plan gh-ensure ${row.dispatch_id} --title "${row.title}"`,
371
+ // House mail contract (round-1 review finding 2): a work order must
372
+ // instruct the recipient to act and reply with the outcome, never
373
+ // bare-acknowledge. `required_action` is the dedicated field
374
+ // read/inbox render FIRST (delivery gap #9) — reusing the shared
375
+ // ACT_DIRECTIVE_CORE text here (not hand-rolled) keeps this nudge
376
+ // consistent with every other surface that delivers mail. The
377
+ // watch-loop-only REARM suffix (ACT_DIRECTIVE) does not apply — this
378
+ // is a one-off nudge, not a standing-loop emission.
379
+ requiredAction: ACT_DIRECTIVE_CORE,
380
+ });
381
+ nudged.push({ dispatchId: row.dispatch_id, ticketId: row.ticket_id, recipient: row.recipient_slug });
382
+ }
383
+ catch (sendErr) {
384
+ try {
385
+ await ctx.supabase.rpc("plan_gh_sweep_release", { p_dispatch_id: row.dispatch_id });
386
+ }
387
+ catch (releaseErr) {
388
+ // Best-effort: if the release itself fails too, the watermark stays
389
+ // set and this dispatch simply waits out the cooldown like any other
390
+ // transient miss would — surfaced here, not fatal to the batch.
391
+ console.error(`plan gh-sweep: dispatch ${row.dispatch_id} failed to send AND failed to release its watermark `
392
+ + `(send: ${sendErr.message}; release: ${releaseErr.message})`);
393
+ continue;
394
+ }
395
+ console.error(`plan gh-sweep: dispatch ${row.dispatch_id} failed to send, watermark released for retry: ${sendErr.message}`);
396
+ }
397
+ }
398
+ return { nudged };
399
+ }
400
+ export function registerPlanGh(plan, g) {
401
+ g(plan.command("gh-ensure").argument("<dispatch-id>"))
402
+ .option("--ticket <uuid>", "cross-check only — the server derives the ticket from the dispatch itself; a mismatch is rejected before any GitHub call")
403
+ .option("--repo <slug>", "cross-check only — the server derives the repo from the dispatch's ticket; a mismatch is rejected before any GitHub call")
404
+ .option("--title <t>", "issue title to use if a new issue must be created (defaults to the ticket's own title)")
405
+ .option("--body <b>", "extra issue body text (the marker line is always appended)")
406
+ .action(async (dispatchId, o) => {
407
+ const ctx = await makeCtx(resolveConfig(o));
408
+ const result = await ghEnsureCore(ctx, {
409
+ dispatchId: Number(dispatchId), ticketId: o.ticket, repo: o.repo, title: o.title, body: o.body,
410
+ });
411
+ console.log(render([{ dispatch_id: Number(dispatchId), ref: result.ref, state: result.state }], { json: !!o.json }));
412
+ });
413
+ g(plan.command("gh-sweep"))
414
+ .option("--stale-hours <n>", "hours pending before a dispatch qualifies for a nudge", "1")
415
+ .option("--cooldown-hours <n>", "hours to wait before re-nudging the same dispatch", "1")
416
+ .action(async (o) => {
417
+ const ctx = await makeCtx(resolveConfig(o));
418
+ const result = await ghSweepCore(ctx, {
419
+ staleSeconds: Number(o.staleHours ?? "1") * 3600,
420
+ cooldownSeconds: Number(o.cooldownHours ?? "1") * 3600,
421
+ });
422
+ console.log(render(result.nudged.map((n) => ({ dispatch_id: n.dispatchId, ticket_id: n.ticketId, nudged: n.recipient })), { json: !!o.json }));
423
+ });
424
+ }
425
+ //# sourceMappingURL=plan-gh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-gh.js","sourceRoot":"","sources":["../../src/commands/plan-gh.ts"],"names":[],"mappings":"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"}