@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,400 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* graph-publish.mjs — SP-B build-ticket "Lane 2" CI-caller rollout kit.
|
|
4
|
+
*
|
|
5
|
+
* Runs in the reusable workflow (.github/workflows/graphify-publish.yml) on
|
|
6
|
+
* every push-to-main. Implements the spec's two non-negotiables:
|
|
7
|
+
* - SHA-identity: graph_head_report is ALWAYS called first, before the
|
|
8
|
+
* build even starts, so the hub learns about a new tip even if the
|
|
9
|
+
* build subsequently fails.
|
|
10
|
+
* - Honest failure: a failed build or failed validation EXITS NON-ZERO.
|
|
11
|
+
* This deliberately does NOT follow the older scripts/graphify-ci.mjs
|
|
12
|
+
* pattern of "exit 0 to not block CI" — that pattern predates the 0050
|
|
13
|
+
* graph-knowledge freshness contract, where a build failure is supposed
|
|
14
|
+
* to leave the repo honestly STALE (via the already-reported head)
|
|
15
|
+
* rather than pretend nothing happened. A red CI check is the correct,
|
|
16
|
+
* visible signal.
|
|
17
|
+
*
|
|
18
|
+
* Round-1 review (PR #33, CB-MSG-005318) fixes applied here:
|
|
19
|
+
* 1 BLOCKER — no more SUPABASE_SERVICE_ROLE_KEY / GRAPH_REPO_ID. This
|
|
20
|
+
* script now authenticates with a per-repo, per-scope bearer token
|
|
21
|
+
* (GRAPH_INGEST_TOKEN) via the anon-executable *_with_token RPCs
|
|
22
|
+
* (0059_graph_ingest_tokens.sql) — repo_id is resolved server-side FROM
|
|
23
|
+
* the token, never supplied by this script. A compromised token can
|
|
24
|
+
* only ever act as the one repo_id/scope it was minted for.
|
|
25
|
+
* 3 MAJOR — every subprocess call uses execFileSync with an argument
|
|
26
|
+
* ARRAY (never string interpolation into a shell command), and
|
|
27
|
+
* GRAPHIFY_REPO_ROOT is resolved + verified to stay under
|
|
28
|
+
* GITHUB_WORKSPACE (or cwd, for local runs) before anything touches it
|
|
29
|
+
* — an absolute path or a `..`-escaping value is rejected outright.
|
|
30
|
+
* 9 MAJOR — no residual service-role-shaped credential lives in this
|
|
31
|
+
* script's env at all now; SUPABASE_ANON_KEY is not sensitive (it is
|
|
32
|
+
* the public client key).
|
|
33
|
+
*
|
|
34
|
+
* Round-15 review finding 1 (CONFIRMED, MAJOR): round-13's fix
|
|
35
|
+
* (.github/workflows/graphify-publish.yml checking the caller out into a
|
|
36
|
+
* dedicated `repo/` subdirectory, a SIBLING of `.codebus-kit/`, specifically
|
|
37
|
+
* so graphify could never traverse into the kit) was bypassable through
|
|
38
|
+
* GRAPHIFY_REPO_ROOT itself — this script validated the resolved root
|
|
39
|
+
* against GITHUB_WORKSPACE (the shared ANCESTOR of both `repo/` and
|
|
40
|
+
* `.codebus-kit/`), not against `repo/` itself, so `GRAPHIFY_REPO_ROOT=..`
|
|
41
|
+
* resolved right back to the workspace root (still "inside" that wider
|
|
42
|
+
* boundary) and `../.codebus-kit` resolved directly onto the kit —
|
|
43
|
+
* silently recreating the exact traversal the round-13 fix believed it had
|
|
44
|
+
* closed. Fixed with a SECOND, narrower boundary — GRAPHIFY_ROOT_BOUNDARY
|
|
45
|
+
* — that the workflow now sets to the caller's own `repo/` checkout
|
|
46
|
+
* directory; containment is checked against THAT, not GITHUB_WORKSPACE,
|
|
47
|
+
* and (a caller's own checkout is fully attacker-controlled content, so a
|
|
48
|
+
* planted symlink cannot be ruled out) using the REAL, symlink-resolved
|
|
49
|
+
* filesystem paths on both sides of the comparison, not merely the
|
|
50
|
+
* lexically-normalized ones `resolve()`/`relative()` alone produce.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* node scripts/graph-publish.mjs
|
|
54
|
+
*
|
|
55
|
+
* Required env:
|
|
56
|
+
* SUPABASE_URL, SUPABASE_ANON_KEY — the public API endpoint + anon key
|
|
57
|
+
* (not sensitive; matches every other Supabase client in this repo).
|
|
58
|
+
* GRAPH_INGEST_TOKEN — this repo's 'ingest'-scoped token, minted via
|
|
59
|
+
* graph_ingest_token_mint by a workspace admin/curator and stored as a
|
|
60
|
+
* GitHub Environment secret (see graphify-publish-codebus.yml).
|
|
61
|
+
*
|
|
62
|
+
* Optional env / flags:
|
|
63
|
+
* GITHUB_SHA, GITHUB_RUN_ID, GITHUB_WORKFLOW_REF, GITHUB_REPOSITORY,
|
|
64
|
+
* GITHUB_WORKSPACE — supplied automatically by GitHub Actions; falls back
|
|
65
|
+
* to `git rev-parse HEAD` / cwd / synthetic values for local runs.
|
|
66
|
+
* GRAPHIFY_REPO_ROOT — repo subdirectory to build, RELATIVE to
|
|
67
|
+
* GITHUB_WORKSPACE (default: "."). Resolved and verified to stay inside
|
|
68
|
+
* GRAPHIFY_ROOT_BOUNDARY (round-15 review finding 1) — never accept
|
|
69
|
+
* this as an unvalidated absolute path.
|
|
70
|
+
* GRAPHIFY_ROOT_BOUNDARY — the directory GRAPHIFY_REPO_ROOT must resolve
|
|
71
|
+
* inside of (round-15 review finding 1); set by the reusable workflow
|
|
72
|
+
* to the caller's own checkout directory (`repo/`), which is narrower
|
|
73
|
+
* than — and a sibling of, never an ancestor shared with — `.codebus-
|
|
74
|
+
* kit/`. Falls back to GITHUB_WORKSPACE (this script's original, wider
|
|
75
|
+
* boundary) when unset, for backward compatibility with any caller or
|
|
76
|
+
* local run that hasn't set it.
|
|
77
|
+
* --artifact-path <file> — skip running `graphify`, load this JSON file
|
|
78
|
+
* as the artifact instead (script-level testing
|
|
79
|
+
* against a fixture, per the build ticket).
|
|
80
|
+
* --skip-build — same effect, reads graphify-out/graph.json
|
|
81
|
+
* as-is without re-running graphify.
|
|
82
|
+
* --dry-run — do everything except the final
|
|
83
|
+
* graph_ingest_with_token call (still reports
|
|
84
|
+
* the head and still validates).
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
import { execFileSync } from "node:child_process";
|
|
88
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
89
|
+
import { join, resolve, relative, isAbsolute } from "node:path";
|
|
90
|
+
import { pathToFileURL } from "node:url";
|
|
91
|
+
import { createClient } from "@supabase/supabase-js";
|
|
92
|
+
|
|
93
|
+
const args = process.argv.slice(2);
|
|
94
|
+
const flag = (name) => args.includes(name);
|
|
95
|
+
const opt = (name) => {
|
|
96
|
+
const i = args.indexOf(name);
|
|
97
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const DRY_RUN = flag("--dry-run");
|
|
101
|
+
const SKIP_BUILD = flag("--skip-build") || !!opt("--artifact-path");
|
|
102
|
+
const ARTIFACT_PATH_OVERRIDE = opt("--artifact-path");
|
|
103
|
+
|
|
104
|
+
function die(msg) {
|
|
105
|
+
console.error(`FATAL: ${msg}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function section(title) {
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log(`=== ${title} ===`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Round-1 review finding 3: GRAPHIFY_REPO_ROOT is a workflow_call INPUT —
|
|
115
|
+
// resolve it against the checkout root and verify the result stays INSIDE
|
|
116
|
+
// the allowed boundary before it ever reaches a subprocess argument or a
|
|
117
|
+
// filesystem call. An absolute path or a `..`-escaping relative path is
|
|
118
|
+
// rejected outright, not sanitized/best-effort-fixed.
|
|
119
|
+
//
|
|
120
|
+
// Round-15 review finding 1 (CONFIRMED, MAJOR): the boundary this function
|
|
121
|
+
// checks against is DELIBERATELY narrower than GITHUB_WORKSPACE whenever
|
|
122
|
+
// GRAPHIFY_ROOT_BOUNDARY is set (see the file header) — checking against
|
|
123
|
+
// GITHUB_WORKSPACE alone let `GRAPHIFY_REPO_ROOT=..` resolve right back to
|
|
124
|
+
// the workspace root (still "inside" that wider boundary) and
|
|
125
|
+
// `../.codebus-kit` resolve directly onto the sibling kit checkout,
|
|
126
|
+
// silently recreating the traversal round-13's directory-separation fix
|
|
127
|
+
// believed it had closed. Exported for scripts/test/graph-publish.validate.
|
|
128
|
+
// test.mjs (guarded below by the same import.meta.url check
|
|
129
|
+
// scripts/graph-trust-sample.mjs already established, so importing this
|
|
130
|
+
// module for that test does not itself run the CLI).
|
|
131
|
+
// Throws a plain Error (never calls die()/process.exit itself) — this
|
|
132
|
+
// keeps the function a pure, in-process-testable unit, matching the same
|
|
133
|
+
// convention scripts/graph-trust-sample.mjs's own validateMinGradable()
|
|
134
|
+
// already established (that one throws too, for the identical reason:
|
|
135
|
+
// scripts/test/*.validate.test.mjs needs to assert a rejection without the
|
|
136
|
+
// test runner's own process getting killed by a process.exit() inside the
|
|
137
|
+
// function under test). main()'s own caller below converts any thrown
|
|
138
|
+
// error from this function into the SAME "FATAL: <message>" die() output
|
|
139
|
+
// as before this refactor — nothing about the CLI's actual observable
|
|
140
|
+
// behavior (message text, exit code 1) changed, only that the failure is
|
|
141
|
+
// now surfaced via a normal thrown Error rather than an in-function
|
|
142
|
+
// process.exit().
|
|
143
|
+
export function resolveRepoRoot() {
|
|
144
|
+
const workspaceRoot = process.env.GITHUB_WORKSPACE || process.cwd();
|
|
145
|
+
const boundaryRaw = process.env.GRAPHIFY_ROOT_BOUNDARY || workspaceRoot;
|
|
146
|
+
const raw = process.env.GRAPHIFY_REPO_ROOT || ".";
|
|
147
|
+
if (isAbsolute(raw)) {
|
|
148
|
+
throw new Error(`GRAPHIFY_REPO_ROOT must be a path relative to the checkout root, got an absolute path: ${raw}`);
|
|
149
|
+
}
|
|
150
|
+
const resolved = resolve(workspaceRoot, raw);
|
|
151
|
+
|
|
152
|
+
// Round-15 review finding 1: containment is checked against the REAL
|
|
153
|
+
// (symlink-resolved) filesystem paths on BOTH sides, not merely the
|
|
154
|
+
// lexically-normalized ones `resolve()`/`relative()` alone would compare
|
|
155
|
+
// — the caller's own checkout is fully attacker-controlled content, so a
|
|
156
|
+
// planted symlink that is lexically "inside" the boundary but actually
|
|
157
|
+
// points elsewhere on disk must not be trusted at face value.
|
|
158
|
+
let realResolved;
|
|
159
|
+
try {
|
|
160
|
+
realResolved = realpathSync(resolved);
|
|
161
|
+
} catch {
|
|
162
|
+
throw new Error(`GRAPHIFY_REPO_ROOT ("${raw}") resolves to a path that does not exist: ${resolved}`);
|
|
163
|
+
}
|
|
164
|
+
let realBoundary;
|
|
165
|
+
try {
|
|
166
|
+
realBoundary = realpathSync(boundaryRaw);
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error(`GRAPHIFY_ROOT_BOUNDARY does not exist: ${boundaryRaw}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const rel = relative(realBoundary, realResolved);
|
|
172
|
+
if (rel === "") return realResolved; // the boundary itself
|
|
173
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
174
|
+
throw new Error(`GRAPHIFY_REPO_ROOT ("${raw}") resolves outside the allowed checkout boundary (${realBoundary}): ${realResolved}`);
|
|
175
|
+
}
|
|
176
|
+
return realResolved;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function main() {
|
|
180
|
+
section("graph-publish: SP-B rollout-kit CI caller");
|
|
181
|
+
|
|
182
|
+
let repoRoot;
|
|
183
|
+
try {
|
|
184
|
+
repoRoot = resolveRepoRoot();
|
|
185
|
+
} catch (e) {
|
|
186
|
+
die(e.message);
|
|
187
|
+
}
|
|
188
|
+
const supabaseUrl = process.env.SUPABASE_URL;
|
|
189
|
+
const anonKey = process.env.SUPABASE_ANON_KEY;
|
|
190
|
+
const ingestToken = process.env.GRAPH_INGEST_TOKEN;
|
|
191
|
+
|
|
192
|
+
// Fail closed: every one of these is load-bearing. Missing config is
|
|
193
|
+
// refused, never silently skipped (house rule, sp-b-knowledge-layer.md
|
|
194
|
+
// §B.2 preamble).
|
|
195
|
+
if (!supabaseUrl) die("SUPABASE_URL is required");
|
|
196
|
+
if (!anonKey) die("SUPABASE_ANON_KEY is required");
|
|
197
|
+
if (!ingestToken) die("GRAPH_INGEST_TOKEN is required (this repo's ingest-scoped token — mint via graph_ingest_token_mint)");
|
|
198
|
+
|
|
199
|
+
let commitSha = process.env.GITHUB_SHA;
|
|
200
|
+
if (!commitSha) {
|
|
201
|
+
try {
|
|
202
|
+
commitSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf-8" }).trim();
|
|
203
|
+
} catch {
|
|
204
|
+
die("could not resolve commit sha (no GITHUB_SHA and `git rev-parse HEAD` failed)");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const runId = process.env.GITHUB_RUN_ID || `local-${Date.now()}`;
|
|
208
|
+
const workflowRef = process.env.GITHUB_WORKFLOW_REF || process.env.GITHUB_REPOSITORY || "local";
|
|
209
|
+
|
|
210
|
+
console.log(`repo root: ${repoRoot}`);
|
|
211
|
+
console.log(`commit sha: ${commitSha}`);
|
|
212
|
+
console.log(`run_id: ${runId}`);
|
|
213
|
+
console.log(`workflow_ref: ${workflowRef}`);
|
|
214
|
+
console.log(`dry-run: ${DRY_RUN}`);
|
|
215
|
+
console.log(`skip-build: ${SKIP_BUILD}`);
|
|
216
|
+
|
|
217
|
+
const supabase = createClient(supabaseUrl, anonKey, { auth: { persistSession: false } });
|
|
218
|
+
|
|
219
|
+
// ── Step 1: report the head FIRST, unconditionally, before any build
|
|
220
|
+
// work happens. This is what makes freshness honest even when step 2
|
|
221
|
+
// fails below — the hub already knows main advanced. Token-scoped: no
|
|
222
|
+
// repo_id is sent, it's resolved server-side from GRAPH_INGEST_TOKEN.
|
|
223
|
+
section("Step 1: graph_head_report_with_token (authoritative tip, before build)");
|
|
224
|
+
{
|
|
225
|
+
const { error } = await supabase.rpc("graph_head_report_with_token", {
|
|
226
|
+
p_token: ingestToken,
|
|
227
|
+
p_sha: commitSha,
|
|
228
|
+
p_run_id: String(runId),
|
|
229
|
+
});
|
|
230
|
+
if (error) die(`graph_head_report_with_token failed: ${error.message} (nothing downstream can be trusted if the tip itself was never recorded)`);
|
|
231
|
+
console.log(`✓ head reported: sha=${commitSha}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Step 2: build (unless testing against a fixture/existing artifact).
|
|
235
|
+
section("Step 2: graphify build");
|
|
236
|
+
const graphJsonPath = join(repoRoot, "graphify-out", "graph.json");
|
|
237
|
+
if (!SKIP_BUILD) {
|
|
238
|
+
try {
|
|
239
|
+
execFileSync("which", ["graphify"], { stdio: "ignore" });
|
|
240
|
+
} catch {
|
|
241
|
+
die("graphify command not found (install via: pip install graphifyy)");
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
execFileSync("graphify", ["update", repoRoot, "--force"], { stdio: "inherit", cwd: repoRoot });
|
|
245
|
+
console.log("✓ graphify build succeeded");
|
|
246
|
+
} catch (err) {
|
|
247
|
+
// HONEST FAILURE: exit non-zero. The head was already reported in
|
|
248
|
+
// Step 1, so graph_status will correctly go STALE after grace
|
|
249
|
+
// instead of silently keeping last-good presented as current.
|
|
250
|
+
die(`graphify build failed (${err.message}). Head sha ${commitSha} was already reported — the repo will honestly go STALE after the configured grace period rather than silently serve a stale artifact as current.`);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
console.log(`(--skip-build/--artifact-path given: not invoking graphify)`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const artifactPath = ARTIFACT_PATH_OVERRIDE || graphJsonPath;
|
|
257
|
+
if (!existsSync(artifactPath)) {
|
|
258
|
+
die(`artifact not found at ${artifactPath}`);
|
|
259
|
+
}
|
|
260
|
+
let artifact;
|
|
261
|
+
try {
|
|
262
|
+
artifact = JSON.parse(readFileSync(artifactPath, "utf-8"));
|
|
263
|
+
} catch (err) {
|
|
264
|
+
die(`failed to parse artifact JSON at ${artifactPath}: ${err.message}`);
|
|
265
|
+
}
|
|
266
|
+
const nodeCount = artifact.nodes?.length ?? 0;
|
|
267
|
+
const edgeCount = (artifact.links ?? artifact.edges ?? []).length;
|
|
268
|
+
const hyperedgeCount = (artifact.hyperedges ?? artifact.graph?.hyperedges ?? []).length;
|
|
269
|
+
console.log(`artifact: ${artifactPath}`);
|
|
270
|
+
console.log(` nodes=${nodeCount} edges=${edgeCount} hyperedges=${hyperedgeCount}`);
|
|
271
|
+
|
|
272
|
+
// ── Step 3: resolve the current vocab version (never hardcode — the
|
|
273
|
+
// vocabulary is the versioned artifact, per B.2.3). graph_vocab_current()
|
|
274
|
+
// is anon-executable (vocab is not repo-scoped/sensitive; SECURITY
|
|
275
|
+
// DEFINER bypasses the graph_vocab_read RLS policy's "any AUTHENTICATED
|
|
276
|
+
// agent" bar, which anon does not clear).
|
|
277
|
+
section("Step 3: resolve current vocab_version");
|
|
278
|
+
let vocabVersion;
|
|
279
|
+
{
|
|
280
|
+
const { data, error } = await supabase.rpc("graph_vocab_current");
|
|
281
|
+
if (error) die(`failed to resolve current vocab_version: ${error.message}`);
|
|
282
|
+
if (!data) die("no graph_vocab row has is_current=true — cannot publish without a current vocabulary version");
|
|
283
|
+
vocabVersion = data;
|
|
284
|
+
console.log(`✓ current vocab_version: ${vocabVersion}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Step 4: pre-validate LOCALLY (same validation shape the ingest RPC
|
|
288
|
+
// itself uses — graph_ingest_validate_anon nests a call straight through
|
|
289
|
+
// to 0050's graph_ingest_validate, the exact function graph_ingest_artifact
|
|
290
|
+
// calls internally) so a schema problem is reported as a clearly-labeled
|
|
291
|
+
// VALIDATION failure, distinct from an INGEST failure, before we ever
|
|
292
|
+
// attempt to publish. Calling graph_ingest_validate DIRECTLY as anon does
|
|
293
|
+
// not work — it is a plain (non-definer) function, so its own internal
|
|
294
|
+
// graph_vocab read still runs under anon's insufficient privileges;
|
|
295
|
+
// graph_ingest_validate_anon is a thin SECURITY DEFINER wrapper added
|
|
296
|
+
// specifically so this pre-check needs no broader credential either.
|
|
297
|
+
section("Step 4: validate artifact against vocab (graph_ingest_validate_anon)");
|
|
298
|
+
{
|
|
299
|
+
const { error } = await supabase.rpc("graph_ingest_validate_anon", {
|
|
300
|
+
p_vocab_version: vocabVersion,
|
|
301
|
+
p_artifact: artifact,
|
|
302
|
+
});
|
|
303
|
+
if (error) die(`VALIDATION FAILED: ${error.message}`);
|
|
304
|
+
console.log("✓ artifact validates against the current vocabulary");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (DRY_RUN) {
|
|
308
|
+
console.log("");
|
|
309
|
+
console.log("=== DRY-RUN: skipping graph_ingest_with_token ===");
|
|
310
|
+
process.exit(0);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── Step 5: publish. Atomic on the DB side: a rejection here leaves
|
|
314
|
+
// last-good serving completely untouched (0050's own guarantee), and
|
|
315
|
+
// (finding 2) the exact validated artifact is persisted content-addressed
|
|
316
|
+
// in the SAME transaction as promotion.
|
|
317
|
+
section("Step 5: graph_ingest_with_token (publish)");
|
|
318
|
+
{
|
|
319
|
+
const { data, error } = await supabase.rpc("graph_ingest_with_token", {
|
|
320
|
+
p_token: ingestToken,
|
|
321
|
+
p_source_sha: commitSha,
|
|
322
|
+
p_vocab_version: vocabVersion,
|
|
323
|
+
p_run_id: String(runId),
|
|
324
|
+
p_workflow_ref: workflowRef,
|
|
325
|
+
p_artifact: artifact,
|
|
326
|
+
});
|
|
327
|
+
if (error) die(`INGEST FAILED: ${error.message}`);
|
|
328
|
+
|
|
329
|
+
console.log(`status: ${data.status}`);
|
|
330
|
+
console.log(`promoted: ${data.promoted}`);
|
|
331
|
+
console.log(`version: ${data.version_id}`);
|
|
332
|
+
console.log(`nodes/edges: ${data.node_count}/${data.edge_count}`);
|
|
333
|
+
|
|
334
|
+
if (data.status === "quarantined") {
|
|
335
|
+
console.log("");
|
|
336
|
+
console.log("⚠ QUARANTINED: this version was NOT promoted (poisoning-guard sanity delta tripped).");
|
|
337
|
+
console.log(" Last-good stays serving. A curator/owner must review before this can be promoted.");
|
|
338
|
+
console.log(" (This is a successful, honest CI run — the guard did its job — not a build failure.)");
|
|
339
|
+
} else if (data.status === "noop_already_ingested") {
|
|
340
|
+
console.log("");
|
|
341
|
+
console.log("(idempotent no-op: this sha was already ingested)");
|
|
342
|
+
} else if (data.promoted) {
|
|
343
|
+
console.log("");
|
|
344
|
+
console.log("✓ promoted to serving (artifact persisted content-addressed, finding 2)");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log("");
|
|
349
|
+
console.log("=== SUCCESS ===");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Round-11 review finding (CONFIRMED, MAJOR): the previous check compared
|
|
353
|
+
// `import.meta.url` (a properly percent-ENCODED URL — Node encodes spaces
|
|
354
|
+
// as %20, '#' as %23, '%' as %25, and on Windows produces a
|
|
355
|
+
// `file:///C:/...`-shaped URL) against `file://${process.argv[1]}`, a raw
|
|
356
|
+
// filesystem path concatenated onto a `file://` prefix with NO encoding at
|
|
357
|
+
// all. For any invocation path containing a space, '#', '%', or run on
|
|
358
|
+
// Windows, the two strings can never be equal even when this IS the
|
|
359
|
+
// directly-executed entry script — the guard silently evaluates false,
|
|
360
|
+
// `main()` never runs, and the process exits 0 having done nothing. Since
|
|
361
|
+
// `.github/workflows/graphify-publish.yml` invokes this exact script (via
|
|
362
|
+
// the CLI's shipped `dist/kit/graph-publish.mjs` copy, or the checked-out
|
|
363
|
+
// `.codebus-kit/scripts/graph-publish.mjs` copy) with `node <path>`, a
|
|
364
|
+
// silent no-op here means `graph_head_report_with_token` is never called,
|
|
365
|
+
// the build never runs, and validate/ingest never happen — while `codebus
|
|
366
|
+
// repo connect --build` (cli/src/commands/repo.ts), which already minted
|
|
367
|
+
// the token and transitioned pending -> ci_wired before spawning this
|
|
368
|
+
// process, sees only the exit code (0) and falsely reports success. Fixed
|
|
369
|
+
// by comparing two properly normalized URLs instead of a URL against a raw
|
|
370
|
+
// path: `pathToFileURL(...).href` applies the SAME percent-encoding rules
|
|
371
|
+
// `import.meta.url` itself was produced with (and produces the correct
|
|
372
|
+
// `file:///C:/...` shape on Windows), so both sides of the comparison are
|
|
373
|
+
// now in the same representation. `realpathSync` on `process.argv[1]`
|
|
374
|
+
// first (symlink-resolved, matching this file's own
|
|
375
|
+
// GRAPHIFY_ROOT_BOUNDARY containment check above, and
|
|
376
|
+
// resolveTrustedGraphPublishScript's identical convention in
|
|
377
|
+
// cli/src/commands/repo.ts) handles the case where the invoked path is a
|
|
378
|
+
// symlink — Node's own ESM loader resolves `import.meta.url` to the
|
|
379
|
+
// REAL underlying file, so comparing against the un-resolved argv[1] would
|
|
380
|
+
// reintroduce an equivalent false-negative for a symlinked invocation.
|
|
381
|
+
// Wrapped in a try/catch since `process.argv[1]` is not guaranteed to name
|
|
382
|
+
// an existing path in every embedding context (e.g. `node -e`); any
|
|
383
|
+
// failure there simply means "this is not a direct file invocation",
|
|
384
|
+
// exactly like the string comparison it replaces.
|
|
385
|
+
//
|
|
386
|
+
// scripts/graph-trust-sample.mjs established the ORIGINAL (buggy) form of
|
|
387
|
+
// this guard; fixed there too, identically, in the same round.
|
|
388
|
+
let isMainModule = false;
|
|
389
|
+
try {
|
|
390
|
+
isMainModule = pathToFileURL(realpathSync(process.argv[1])).href === import.meta.url;
|
|
391
|
+
} catch {
|
|
392
|
+
// process.argv[1] does not resolve to a real path — not a direct
|
|
393
|
+
// file invocation; isMainModule stays false, matching the old guard's
|
|
394
|
+
// behavior for the same case.
|
|
395
|
+
}
|
|
396
|
+
if (isMainModule) {
|
|
397
|
+
main().catch((err) => {
|
|
398
|
+
die(err.stack || err.message || String(err));
|
|
399
|
+
});
|
|
400
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Shared by doctor.ts and checkin.ts (delivery gap #8): under Claude Code,
|
|
2
|
+
// checkin deliberately skips auto-arming a watch window because the Stop hook
|
|
3
|
+
// already surfaces mail at turn boundaries — but that hook is opt-in
|
|
4
|
+
// (`codebus install claude`), identity-gated, and only fires at turn
|
|
5
|
+
// boundaries. If NEITHER the Stop hook nor a live/recent watch window is
|
|
6
|
+
// present, mail latency for that agent is genuinely unbounded: nothing pulls
|
|
7
|
+
// new mail until a human happens to poll. Both doctor and checkin must flag
|
|
8
|
+
// this loudly rather than silently assuming "checkin ran, so we're fine".
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { workspaceId } from "./client.js";
|
|
13
|
+
export function stopHookInstalled() {
|
|
14
|
+
try {
|
|
15
|
+
const s = JSON.parse(readFileSync(join(homedir(), ".claude", "settings.json"), "utf8"));
|
|
16
|
+
return JSON.stringify(s?.hooks?.Stop ?? []).includes("codebus tick");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// RC3: Codex's native lifecycle-hooks engine (codex-rs/hooks; Feature::CodexHooks,
|
|
23
|
+
// stage Stable, default_enabled true) is a genuine turn-boundary hook, not the
|
|
24
|
+
// older fire-and-forget `notify` shim most blog posts describe. Its Stop event
|
|
25
|
+
// carries the same `stop_hook_active` loop-guard field Claude Code's Stop hook
|
|
26
|
+
// does, and a handler can return `{"decision":"block","reason":"..."}` on
|
|
27
|
+
// stdout to feed `reason` back as the NEXT user prompt — verified against
|
|
28
|
+
// codex-rs/hooks/src/events/stop.rs, which is functionally identical to Claude
|
|
29
|
+
// Code's Stop-hook contract. `codebus install codex` wires exactly the same
|
|
30
|
+
// `codebus tick --hook` command Claude's Stop hook runs into
|
|
31
|
+
// ~/.codex/hooks.json (Stop event) — this checks whether that wiring is present.
|
|
32
|
+
// A hook event is genuinely wired only when one of its groups carries a command
|
|
33
|
+
// handler whose `command` is EXACTLY `cmd`. A substring match over the serialized
|
|
34
|
+
// JSON (the earlier approach) is fooled by any unrelated hook whose description
|
|
35
|
+
// or command merely CONTAINS the text — that both reports false coverage (checkin
|
|
36
|
+
// skips its watch fallback, doctor claims bounded delivery) and makes the
|
|
37
|
+
// installer refuse to add the real hook (ai-review finding). Shape of a hook
|
|
38
|
+
// event: [ { hooks: [ { type: "command", command: "..." } ] } ].
|
|
39
|
+
export function hookEventHasCommand(hookEvent, cmd) {
|
|
40
|
+
if (!Array.isArray(hookEvent))
|
|
41
|
+
return false;
|
|
42
|
+
return hookEvent.some((group) => !!group && typeof group === "object" && Array.isArray(group.hooks) &&
|
|
43
|
+
group.hooks.some((h) => !!h && typeof h === "object" &&
|
|
44
|
+
h.type === "command" &&
|
|
45
|
+
h.command === cmd));
|
|
46
|
+
}
|
|
47
|
+
export function codexHooksInstalled() {
|
|
48
|
+
try {
|
|
49
|
+
const s = JSON.parse(readFileSync(join(homedir(), ".codex", "hooks.json"), "utf8"));
|
|
50
|
+
return hookEventHasCommand(s?.hooks?.Stop, "codebus tick --hook");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Review MAJOR finding #5: a Claude Stop hook being INSTALLED (present in
|
|
57
|
+
// ~/.claude/settings.json) is a machine-wide fact — it says nothing about
|
|
58
|
+
// whether THIS process is actually running inside a Claude Code session. The
|
|
59
|
+
// hook only fires for turns Claude Code itself drives; a Codex session or a
|
|
60
|
+
// plain shell in the same directory never triggers it no matter how it's
|
|
61
|
+
// configured. `CLAUDECODE` is set by Claude Code itself for every session it
|
|
62
|
+
// runs (already relied on by checkin.ts's auto-arm-watch branch) — the one
|
|
63
|
+
// reliable signal that the hook could possibly apply to the current process.
|
|
64
|
+
export function inClaudeCodeSession() {
|
|
65
|
+
return !!process.env.CLAUDECODE;
|
|
66
|
+
}
|
|
67
|
+
// Codex's analogue of CLAUDECODE: CODEX_THREAD_ID is injected by the Codex CLI
|
|
68
|
+
// into every subprocess it spawns for the active thread — shell-tool calls and
|
|
69
|
+
// hook commands alike (codex-rs/protocol/src/shell_environment.rs:
|
|
70
|
+
// CODEX_THREAD_ID_ENV_VAR; codex-rs/core/src/exec_env.rs — "injected when a
|
|
71
|
+
// thread id is provided, even when ..."). It is the one reliable signal that a
|
|
72
|
+
// Codex Stop hook installed in ~/.codex/hooks.json could possibly be firing
|
|
73
|
+
// FOR this process, exactly as CLAUDECODE is for Claude Code above.
|
|
74
|
+
export function inCodexSession() {
|
|
75
|
+
return !!process.env.CODEX_THREAD_ID;
|
|
76
|
+
}
|
|
77
|
+
// Generous relative to the standing loop's own re-arm cadence (directives.ts
|
|
78
|
+
// REARM: 280s) — a watch window arms itself well inside this before going stale.
|
|
79
|
+
export const WATCH_ARM_FRESHNESS_MS = 10 * 60 * 1000;
|
|
80
|
+
// Best-effort: `codebus watch` logs a session_log(kind='watch') row on arm
|
|
81
|
+
// (0048). There is no durable "a process is still blocked in watch" truth
|
|
82
|
+
// available from SQL, so freshness of the last arm is the closest signal.
|
|
83
|
+
// Review MAJOR finding #5: scoped to the SELECTED workspace — an agent that
|
|
84
|
+
// belongs to multiple workspaces previously had a watch armed in workspace A
|
|
85
|
+
// count as coverage for workspace B, which it never watched.
|
|
86
|
+
export async function watchRecentlyArmed(ctx, freshnessMs = WATCH_ARM_FRESHNESS_MS) {
|
|
87
|
+
try {
|
|
88
|
+
const ws = await workspaceId(ctx);
|
|
89
|
+
const { data } = await ctx.supabase
|
|
90
|
+
.from("session_log")
|
|
91
|
+
.select("created_at, agents!inner(slug)")
|
|
92
|
+
.eq("kind", "watch")
|
|
93
|
+
.eq("workspace_id", ws)
|
|
94
|
+
.eq("agents.slug", ctx.agentSlug)
|
|
95
|
+
.order("created_at", { ascending: false })
|
|
96
|
+
.limit(1);
|
|
97
|
+
const row = (data ?? [])[0];
|
|
98
|
+
if (!row)
|
|
99
|
+
return false;
|
|
100
|
+
return Date.now() - new Date(row.created_at).getTime() < freshnessMs;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// True delivery coverage for THIS process: either it's actually running
|
|
107
|
+
// inside a Claude Code session with the Stop hook installed (the hook WILL
|
|
108
|
+
// fire for it), or a watch window is live/recently armed for the selected
|
|
109
|
+
// workspace. A hook merely being installed somewhere on the machine does not
|
|
110
|
+
// count when the current process is Codex/a plain shell — that hook can
|
|
111
|
+
// never fire for a process Claude Code isn't driving (review MAJOR finding #5).
|
|
112
|
+
export async function hasDeliveryCoverage(ctx) {
|
|
113
|
+
const viaHook = (inClaudeCodeSession() && stopHookInstalled()) || (inCodexSession() && codexHooksInstalled());
|
|
114
|
+
const viaWatch = ctx ? await watchRecentlyArmed(ctx) : false;
|
|
115
|
+
return { ok: viaHook || viaWatch, viaHook, viaWatch };
|
|
116
|
+
}
|
|
117
|
+
export const MAIL_LATENCY_WARNING = "mail latency unbounded: no Stop hook installed (codebus install claude, or codebus install codex) and no watch " +
|
|
118
|
+
"armed (codebus watch --mine --first --timeout 280000). Without one of these, new mail sits until " +
|
|
119
|
+
"someone happens to poll — install the hook for your tool, or keep a watch window armed.";
|
|
120
|
+
//# sourceMappingURL=mail-latency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-latency.js","sourceRoot":"","sources":["../src/mail-latency.ts"],"names":[],"mappings":"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"}
|