@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.
- package/bin/codebus.js +2 -0
- package/dist/binding-guard.js +97 -0
- package/dist/binding-guard.js.map +1 -0
- package/dist/client.js +94 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/ack.js +47 -0
- package/dist/commands/ack.js.map +1 -0
- package/dist/commands/archive.js +21 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/as.js +299 -0
- package/dist/commands/as.js.map +1 -0
- package/dist/commands/ask.js +189 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/auth.js +33 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/checkin.js +235 -0
- package/dist/commands/checkin.js.map +1 -0
- package/dist/commands/decision.js +52 -0
- package/dist/commands/decision.js.map +1 -0
- package/dist/commands/digest.js +117 -0
- package/dist/commands/digest.js.map +1 -0
- package/dist/commands/doctor.js +106 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/factory.js +191 -0
- package/dist/commands/factory.js.map +1 -0
- package/dist/commands/github-login.js +205 -0
- package/dist/commands/github-login.js.map +1 -0
- package/dist/commands/graph.js +88 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/health.js +34 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/inbox.js +83 -0
- package/dist/commands/inbox.js.map +1 -0
- package/dist/commands/ingest.js +553 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.js +97 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.js +520 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/invite.js +152 -0
- package/dist/commands/invite.js.map +1 -0
- package/dist/commands/jira.js +237 -0
- package/dist/commands/jira.js.map +1 -0
- package/dist/commands/join.js +96 -0
- package/dist/commands/join.js.map +1 -0
- package/dist/commands/links.js +66 -0
- package/dist/commands/links.js.map +1 -0
- package/dist/commands/login.js +84 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/morning.js +121 -0
- package/dist/commands/morning.js.map +1 -0
- package/dist/commands/next.js +66 -0
- package/dist/commands/next.js.map +1 -0
- package/dist/commands/onboarding.js +109 -0
- package/dist/commands/onboarding.js.map +1 -0
- package/dist/commands/pack.js +276 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/plan-gh.js +425 -0
- package/dist/commands/plan-gh.js.map +1 -0
- package/dist/commands/plan.js +465 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/pr.js +88 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/question.js +142 -0
- package/dist/commands/question.js.map +1 -0
- package/dist/commands/read.js +84 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/refs.js +357 -0
- package/dist/commands/refs.js.map +1 -0
- package/dist/commands/reply.js +38 -0
- package/dist/commands/reply.js.map +1 -0
- package/dist/commands/repo.js +1198 -0
- package/dist/commands/repo.js.map +1 -0
- package/dist/commands/report.js +41 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/resolve-refs.js +21 -0
- package/dist/commands/resolve-refs.js.map +1 -0
- package/dist/commands/resolve.js +93 -0
- package/dist/commands/resolve.js.map +1 -0
- package/dist/commands/send.js +168 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/sent.js +22 -0
- package/dist/commands/sent.js.map +1 -0
- package/dist/commands/session.js +426 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/statusline.js +43 -0
- package/dist/commands/statusline.js.map +1 -0
- package/dist/commands/task.js +140 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/tick.js +233 -0
- package/dist/commands/tick.js.map +1 -0
- package/dist/commands/upgrade.js +76 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/watch.js +169 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/whoami.js +27 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config.js +103 -0
- package/dist/config.js.map +1 -0
- package/dist/directives.js +102 -0
- package/dist/directives.js.map +1 -0
- package/dist/errors.js +110 -0
- package/dist/errors.js.map +1 -0
- package/dist/git-hooks.js +116 -0
- package/dist/git-hooks.js.map +1 -0
- package/dist/graph-bootstrap.js +95 -0
- package/dist/graph-bootstrap.js.map +1 -0
- package/dist/graph-remote.js +889 -0
- package/dist/graph-remote.js.map +1 -0
- package/dist/graph.js +335 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/graph-publish.mjs +400 -0
- package/dist/mail-latency.js +120 -0
- package/dist/mail-latency.js.map +1 -0
- package/dist/mcp-registry.js +859 -0
- package/dist/mcp-registry.js.map +1 -0
- package/dist/mcp.js +193 -0
- package/dist/mcp.js.map +1 -0
- package/dist/output.js +12 -0
- package/dist/output.js.map +1 -0
- package/dist/positioning.js +21 -0
- package/dist/positioning.js.map +1 -0
- package/dist/rolestore.js +89 -0
- package/dist/rolestore.js.map +1 -0
- package/dist/session.js +49 -0
- package/dist/session.js.map +1 -0
- package/dist/sessionstore.js +736 -0
- package/dist/sessionstore.js.map +1 -0
- package/dist/version-check.js +97 -0
- package/dist/version-check.js.map +1 -0
- 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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"}
|