@tokenfactory/acc-runner 0.41.7 → 0.43.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/README.md +87 -4
- package/dist/acc.d.ts +3 -0
- package/dist/acc.d.ts.map +1 -0
- package/dist/acc.js +21 -0
- package/dist/acc.js.map +1 -0
- package/dist/cli-name.d.ts +29 -0
- package/dist/cli-name.d.ts.map +1 -0
- package/dist/cli-name.js +17 -0
- package/dist/cli-name.js.map +1 -0
- package/dist/cli.d.ts +20 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +45 -289
- package/dist/cli.js.map +1 -1
- package/dist/engines/account-probe.d.ts +24 -0
- package/dist/engines/account-probe.d.ts.map +1 -1
- package/dist/engines/account-probe.js +18 -1
- package/dist/engines/account-probe.js.map +1 -1
- package/dist/keychain.d.ts +17 -0
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +36 -0
- package/dist/keychain.js.map +1 -1
- package/dist/login.d.ts +45 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +91 -4
- package/dist/login.js.map +1 -1
- package/dist/machine-capabilities/index.d.ts +21 -0
- package/dist/machine-capabilities/index.d.ts.map +1 -0
- package/dist/machine-capabilities/index.js +21 -0
- package/dist/machine-capabilities/index.js.map +1 -0
- package/dist/machine-capabilities/probe.d.ts +61 -0
- package/dist/machine-capabilities/probe.d.ts.map +1 -0
- package/dist/machine-capabilities/probe.js +243 -0
- package/dist/machine-capabilities/probe.js.map +1 -0
- package/dist/machine-capabilities/redact.d.ts +42 -0
- package/dist/machine-capabilities/redact.d.ts.map +1 -0
- package/dist/machine-capabilities/redact.js +160 -0
- package/dist/machine-capabilities/redact.js.map +1 -0
- package/dist/machine-capabilities/report.d.ts +107 -0
- package/dist/machine-capabilities/report.d.ts.map +1 -0
- package/dist/machine-capabilities/report.js +211 -0
- package/dist/machine-capabilities/report.js.map +1 -0
- package/dist/machine-capabilities/types.d.ts +115 -0
- package/dist/machine-capabilities/types.d.ts.map +1 -0
- package/dist/machine-capabilities/types.js +11 -0
- package/dist/machine-capabilities/types.js.map +1 -0
- package/dist/migration-seq-guard.d.ts +78 -0
- package/dist/migration-seq-guard.d.ts.map +1 -0
- package/dist/migration-seq-guard.js +304 -0
- package/dist/migration-seq-guard.js.map +1 -0
- package/dist/node-version.d.ts +132 -0
- package/dist/node-version.d.ts.map +1 -0
- package/dist/node-version.js +318 -0
- package/dist/node-version.js.map +1 -0
- package/dist/prepr-gate.d.ts +120 -0
- package/dist/prepr-gate.d.ts.map +1 -1
- package/dist/prepr-gate.js +202 -3
- package/dist/prepr-gate.js.map +1 -1
- package/dist/program.d.ts +16 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +355 -0
- package/dist/program.js.map +1 -0
- package/dist/runtime/review-chunking.d.ts +101 -0
- package/dist/runtime/review-chunking.d.ts.map +1 -0
- package/dist/runtime/review-chunking.js +192 -0
- package/dist/runtime/review-chunking.js.map +1 -0
- package/dist/runtime/review-diff.d.ts +30 -0
- package/dist/runtime/review-diff.d.ts.map +1 -0
- package/dist/runtime/review-diff.js +59 -0
- package/dist/runtime/review-diff.js.map +1 -0
- package/dist/runtime/reviewer.d.ts +40 -19
- package/dist/runtime/reviewer.d.ts.map +1 -1
- package/dist/runtime/reviewer.js +252 -51
- package/dist/runtime/reviewer.js.map +1 -1
- package/dist/serve-scope/command.d.ts +12 -0
- package/dist/serve-scope/command.d.ts.map +1 -0
- package/dist/serve-scope/command.js +89 -0
- package/dist/serve-scope/command.js.map +1 -0
- package/dist/serve-scope/index.d.ts +130 -0
- package/dist/serve-scope/index.d.ts.map +1 -0
- package/dist/serve-scope/index.js +148 -0
- package/dist/serve-scope/index.js.map +1 -0
- package/dist/task-runner.d.ts +11 -0
- package/dist/task-runner.d.ts.map +1 -1
- package/dist/task-runner.js +69 -0
- package/dist/task-runner.js.map +1 -1
- package/dist/user-auth/device-login.d.ts +47 -0
- package/dist/user-auth/device-login.d.ts.map +1 -0
- package/dist/user-auth/device-login.js +189 -0
- package/dist/user-auth/device-login.js.map +1 -0
- package/dist/user-auth/liveness.d.ts +27 -0
- package/dist/user-auth/liveness.d.ts.map +1 -0
- package/dist/user-auth/liveness.js +128 -0
- package/dist/user-auth/liveness.js.map +1 -0
- package/dist/user-auth/session.d.ts +82 -0
- package/dist/user-auth/session.d.ts.map +1 -0
- package/dist/user-auth/session.js +172 -0
- package/dist/user-auth/session.js.map +1 -0
- package/dist/user-cli/chat.d.ts +44 -0
- package/dist/user-cli/chat.d.ts.map +1 -0
- package/dist/user-cli/chat.js +199 -0
- package/dist/user-cli/chat.js.map +1 -0
- package/dist/user-cli/client.d.ts +20 -0
- package/dist/user-cli/client.d.ts.map +1 -0
- package/dist/user-cli/client.js +60 -0
- package/dist/user-cli/client.js.map +1 -0
- package/dist/user-cli/tasks.d.ts +37 -0
- package/dist/user-cli/tasks.d.ts.map +1 -0
- package/dist/user-cli/tasks.js +131 -0
- package/dist/user-cli/tasks.js.map +1 -0
- package/dist/watch.d.ts +8 -0
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +28 -0
- package/dist/watch.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AX-H1 (MACHINE-CAPABILITIES): the host probe. Answers "what does THIS machine
|
|
3
|
+
* actually offer?" — gh, git, and each registered engine's login — so ACC can
|
|
4
|
+
* tell a user what their own machine can do instead of leaving that in the
|
|
5
|
+
* operator's head.
|
|
6
|
+
*
|
|
7
|
+
* WHAT IT READS, AND WHAT IT REFUSES TO READ. Every probe here is a read-only,
|
|
8
|
+
* short-timeout, local invocation of a tool's own status command:
|
|
9
|
+
*
|
|
10
|
+
* gh `gh --version`, `gh auth status` — presence, version, whether the
|
|
11
|
+
* credential works, the account login, the host, and the token's SCOPE
|
|
12
|
+
* LABELS. `--show-token` is NEVER passed: the scopes are the authority,
|
|
13
|
+
* and the authority is the answerable question ("can this machine open a
|
|
14
|
+
* PR?"). The token is not read, so it cannot be sent. The unit suite
|
|
15
|
+
* asserts no argv in this package contains `--show-token`.
|
|
16
|
+
* git `git --version`, `git config --get user.email` — presence, version and
|
|
17
|
+
* the committing identity. Git has no host-level login of its own, so
|
|
18
|
+
* `authenticated` is false and the credential-health question is answered
|
|
19
|
+
* by the separate GIT-AUTH-ALERT `ls-remote` probe (git-auth/probe.ts),
|
|
20
|
+
* which is where a remote round-trip belongs.
|
|
21
|
+
* engines the AS-1 `accountIdentity()` surface plus a `--version` presence
|
|
22
|
+
* check. NOT a second credential probe: the identity is exactly what the
|
|
23
|
+
* AS-1 cadence already computes, so the engine rows are a projection of
|
|
24
|
+
* a fact this process already has.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here reads a keychain, a token file, or an environment variable's
|
|
27
|
+
* VALUE. Every free-text field goes through redact.ts before it leaves the host.
|
|
28
|
+
*
|
|
29
|
+
* FULLY injectable (exec / hostname / engines / version) so the whole probe is
|
|
30
|
+
* exercised with zero real subprocesses.
|
|
31
|
+
*/
|
|
32
|
+
import { execa } from "execa";
|
|
33
|
+
import os from "node:os";
|
|
34
|
+
import { buildEnv } from "../bin-resolve.js";
|
|
35
|
+
import { chatEngineIds, getChatEngine } from "../engines/registry.js";
|
|
36
|
+
import { unknownAccountIdentity } from "../engines/account-identity.js";
|
|
37
|
+
import { PACKAGE_VERSION } from "../pkg-version.js";
|
|
38
|
+
import { sanitizeSnapshot } from "./redact.js";
|
|
39
|
+
import { HOST_TOOLS, } from "./types.js";
|
|
40
|
+
/** Per-probe timeout. A capability probe must never hold up a cadence tick. */
|
|
41
|
+
export const PROBE_TIMEOUT_MS = 5_000;
|
|
42
|
+
const defaultExec = async (bin, args) => {
|
|
43
|
+
try {
|
|
44
|
+
const res = await execa(bin, args, {
|
|
45
|
+
env: buildEnv(),
|
|
46
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
47
|
+
reject: false,
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
stdout: res.stdout ?? "",
|
|
51
|
+
stderr: res.stderr ?? "",
|
|
52
|
+
exitCode: res.exitCode ?? null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
// A missing binary throws ENOENT rather than exiting non-zero. That is
|
|
57
|
+
// "absent", not "broken", and it is reported as such.
|
|
58
|
+
return { stdout: "", stderr: err?.message ?? "spawn failed", exitCode: null };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Extract the first dotted version triple from a `--version` line. Tolerant on
|
|
63
|
+
* purpose: `gh version 2.62.0 (2024-11-08)`, `git version 2.46.0`,
|
|
64
|
+
* `1.0.30 (Claude Code)` and a bare `2.4` all resolve; anything else is null
|
|
65
|
+
* rather than a guess (an invented version would make an "is this old enough?"
|
|
66
|
+
* question answerable and wrong).
|
|
67
|
+
*/
|
|
68
|
+
export function parseVersion(output) {
|
|
69
|
+
const m = /\b(\d+(?:\.\d+){1,3})\b/.exec(output ?? "");
|
|
70
|
+
return m ? m[1] : null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse `gh auth status` output. gh prints a human block per host:
|
|
74
|
+
*
|
|
75
|
+
* github.com
|
|
76
|
+
* ✓ Logged in to github.com account gaurav (keyring)
|
|
77
|
+
* - Active account: true
|
|
78
|
+
* - Git operations protocol: https
|
|
79
|
+
* - Token: gho_************************************
|
|
80
|
+
* - Token scopes: 'gist', 'read:org', 'repo'
|
|
81
|
+
*
|
|
82
|
+
* The `Token:` line is already elided by gh, and we do not read it either way —
|
|
83
|
+
* only the account, the host, and the scope labels. Tolerant of the older
|
|
84
|
+
* `Logged in to github.com as gaurav (…)` spelling, and of `--json`-less GHES
|
|
85
|
+
* output where the host is the bare first line.
|
|
86
|
+
*/
|
|
87
|
+
export function parseGhAuthStatus(output) {
|
|
88
|
+
const text = output ?? "";
|
|
89
|
+
const account = /Logged in to \S+ account ([A-Za-z0-9._-]+)/.exec(text)?.[1] ??
|
|
90
|
+
/Logged in to \S+ as ([A-Za-z0-9._-]+)/.exec(text)?.[1] ??
|
|
91
|
+
null;
|
|
92
|
+
const host = /Logged in to (\S+?)(?: account| as)\b/.exec(text)?.[1] ?? null;
|
|
93
|
+
const scopeLine = /Token scopes:\s*(.+)/.exec(text)?.[1] ?? "";
|
|
94
|
+
const scopes = scopeLine
|
|
95
|
+
.split(",")
|
|
96
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ""))
|
|
97
|
+
.filter((s) => s.length > 0 && s !== "none");
|
|
98
|
+
return { account, host, scopes };
|
|
99
|
+
}
|
|
100
|
+
/** Probe `gh`: presence, version, whether it authenticates, and as whom. */
|
|
101
|
+
async function probeGh(exec) {
|
|
102
|
+
const ver = await exec("gh", ["--version"]);
|
|
103
|
+
const present = ver.exitCode === 0;
|
|
104
|
+
const version = present ? parseVersion(ver.stdout) : null;
|
|
105
|
+
if (!present) {
|
|
106
|
+
return {
|
|
107
|
+
tool: "gh",
|
|
108
|
+
present: false,
|
|
109
|
+
version: null,
|
|
110
|
+
authenticated: false,
|
|
111
|
+
identity: null,
|
|
112
|
+
host: null,
|
|
113
|
+
scopes: [],
|
|
114
|
+
detail: "gh not installed (or not on PATH)",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// `gh auth status` is a LOCAL token check, not a GitHub API call (see gh.ts),
|
|
118
|
+
// so it is unbudgeted and safe to run on every capability probe.
|
|
119
|
+
const status = await exec("gh", ["auth", "status"]);
|
|
120
|
+
const authenticated = status.exitCode === 0;
|
|
121
|
+
// gh has printed the status block on stdout since 2.x and on stderr before
|
|
122
|
+
// that; read both so an older install still resolves an identity.
|
|
123
|
+
const { account, host, scopes } = parseGhAuthStatus(`${status.stdout}\n${status.stderr}`);
|
|
124
|
+
return {
|
|
125
|
+
tool: "gh",
|
|
126
|
+
present: true,
|
|
127
|
+
version,
|
|
128
|
+
authenticated,
|
|
129
|
+
identity: authenticated ? account : null,
|
|
130
|
+
host: authenticated ? host : null,
|
|
131
|
+
scopes: authenticated ? scopes : [],
|
|
132
|
+
detail: authenticated
|
|
133
|
+
? `gh ${version ?? "?"} authenticated${account ? ` as ${account}` : ""}${host ? ` on ${host}` : ""}`
|
|
134
|
+
: `gh ${version ?? "?"} installed but not authenticated (run \`gh auth login\`)`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Probe `git`: presence, version and the committing identity. */
|
|
138
|
+
async function probeGit(exec) {
|
|
139
|
+
const ver = await exec("git", ["--version"]);
|
|
140
|
+
const present = ver.exitCode === 0;
|
|
141
|
+
if (!present) {
|
|
142
|
+
return {
|
|
143
|
+
tool: "git",
|
|
144
|
+
present: false,
|
|
145
|
+
version: null,
|
|
146
|
+
authenticated: false,
|
|
147
|
+
identity: null,
|
|
148
|
+
host: null,
|
|
149
|
+
scopes: [],
|
|
150
|
+
detail: "git not installed (or not on PATH)",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const version = parseVersion(ver.stdout);
|
|
154
|
+
const email = await exec("git", ["config", "--get", "user.email"]);
|
|
155
|
+
const identity = email.exitCode === 0 ? email.stdout.trim() || null : null;
|
|
156
|
+
return {
|
|
157
|
+
tool: "git",
|
|
158
|
+
present: true,
|
|
159
|
+
version,
|
|
160
|
+
// git holds no host login of its own; remote credential health is the
|
|
161
|
+
// GIT-AUTH-ALERT ls-remote probe's job, and duplicating it here would mean
|
|
162
|
+
// a second remote round trip on every cadence tick.
|
|
163
|
+
authenticated: false,
|
|
164
|
+
identity,
|
|
165
|
+
host: null,
|
|
166
|
+
scopes: [],
|
|
167
|
+
detail: identity
|
|
168
|
+
? `git ${version ?? "?"} committing as ${identity}`
|
|
169
|
+
: `git ${version ?? "?"} (no user.email configured)`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Probe one engine: is the binary there, and who is it logged in as? The identity
|
|
174
|
+
* comes from the AS-1 `accountIdentity()` surface — this is a PROJECTION of the
|
|
175
|
+
* probe the cadence already runs, not a second credential probe. An engine that
|
|
176
|
+
* predates AS-1 degrades to the honest unknown-account identity rather than being
|
|
177
|
+
* reported as logged out.
|
|
178
|
+
*/
|
|
179
|
+
async function probeEngine(engineId, exec) {
|
|
180
|
+
let binary = engineId;
|
|
181
|
+
let channel = "none";
|
|
182
|
+
let identity = unknownAccountIdentity({
|
|
183
|
+
engineId,
|
|
184
|
+
channel: "none",
|
|
185
|
+
detail: `engine ${engineId} is not registered in this runner build`,
|
|
186
|
+
});
|
|
187
|
+
try {
|
|
188
|
+
const engine = getChatEngine(engineId);
|
|
189
|
+
binary = engine.binary;
|
|
190
|
+
channel = engine.authMode;
|
|
191
|
+
identity = engine.accountIdentity
|
|
192
|
+
? await engine.accountIdentity()
|
|
193
|
+
: unknownAccountIdentity({
|
|
194
|
+
engineId: engine.id,
|
|
195
|
+
channel: engine.authMode,
|
|
196
|
+
detail: "no account-identity probe on this engine build",
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// An unknown / unresolvable engine id is reported as absent, never thrown:
|
|
201
|
+
// one bad engine must not cost the whole snapshot.
|
|
202
|
+
}
|
|
203
|
+
const ver = await exec(binary, ["--version"]);
|
|
204
|
+
const present = ver.exitCode === 0;
|
|
205
|
+
return {
|
|
206
|
+
engine_id: engineId,
|
|
207
|
+
present,
|
|
208
|
+
version: present ? parseVersion(ver.stdout) : null,
|
|
209
|
+
// "Logged in" means a REAL named account resolved. The AS-1 honest-unknown
|
|
210
|
+
// identity always carries a synthetic account, so presence of that id is not
|
|
211
|
+
// evidence of a login — the email is.
|
|
212
|
+
logged_in: identity.account_email !== null,
|
|
213
|
+
channel: identity.channel ?? channel,
|
|
214
|
+
account_email: identity.account_email,
|
|
215
|
+
plan_hint: identity.plan_hint,
|
|
216
|
+
synthetic_account: identity.synthetic_account,
|
|
217
|
+
detail: present
|
|
218
|
+
? identity.detail
|
|
219
|
+
: `${engineId} not installed (or not on PATH); ${identity.detail}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Probe the whole host. Never throws and never rejects: a probe that fails is
|
|
224
|
+
* reported as an absent tool, because "we could not tell" and "it is not there"
|
|
225
|
+
* are both answers a user can act on, whereas an exception on a cadence tick is
|
|
226
|
+
* not. The result is passed through `sanitizeSnapshot` before it is returned, so
|
|
227
|
+
* a caller cannot obtain an unredacted snapshot from this module at all.
|
|
228
|
+
*/
|
|
229
|
+
export async function probeMachineCapabilities(deps = {}) {
|
|
230
|
+
const exec = deps.exec ?? defaultExec;
|
|
231
|
+
const engineIds = (deps.engineIds ?? chatEngineIds)();
|
|
232
|
+
const tools = await Promise.all(HOST_TOOLS.map((tool) => (tool === "gh" ? probeGh(exec) : probeGit(exec))));
|
|
233
|
+
const engines = await Promise.all(engineIds.map((id) => probeEngine(id, exec)));
|
|
234
|
+
return sanitizeSnapshot({
|
|
235
|
+
machine: (deps.hostname ?? (() => os.hostname()))() || "unknown-host",
|
|
236
|
+
platform: deps.platform ?? process.platform,
|
|
237
|
+
arch: deps.arch ?? process.arch,
|
|
238
|
+
runner_version: deps.runnerVersion ?? PACKAGE_VERSION,
|
|
239
|
+
tools,
|
|
240
|
+
engines,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/machine-capabilities/probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,UAAU,GAIX,MAAM,YAAY,CAAC;AAYpB,+EAA+E;AAC/E,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,MAAM,WAAW,GAAc,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YACjC,GAAG,EAAE,QAAQ,EAAE;YACf,OAAO,EAAE,gBAAgB;YACzB,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;QACH,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;YACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;SAC/B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,uEAAuE;QACvE,sDAAsD;QACtD,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAG,GAAa,EAAE,OAAO,IAAI,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3F,CAAC;AACH,CAAC,CAAC;AAgBF;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAK9C,MAAM,IAAI,GAAG,MAAM,IAAI,EAAE,CAAC;IAC1B,MAAM,OAAO,GACX,4CAA4C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,uCAAuC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC;IACP,MAAM,IAAI,GAAG,uCAAuC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC7E,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,MAAM,MAAM,GAAG,SAAS;SACrB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;SAChD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC;IAC/C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACnC,CAAC;AAED,4EAA4E;AAC5E,KAAK,UAAU,OAAO,CAAC,IAAe;IACpC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,KAAK;YACpB,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,mCAAmC;SAC5C,CAAC;IACJ,CAAC;IACD,8EAA8E;IAC9E,iEAAiE;IACjE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,kEAAkE;IAClE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,CACjD,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CACrC,CAAC;IACF,OAAO;QACL,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,IAAI;QACb,OAAO;QACP,aAAa;QACb,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACxC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;QACjC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QACnC,MAAM,EAAE,aAAa;YACnB,CAAC,CAAC,MAAM,OAAO,IAAI,GAAG,iBAAiB,OAAO,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAClE,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EACzB,EAAE;YACJ,CAAC,CAAC,MAAM,OAAO,IAAI,GAAG,0DAA0D;KACnF,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,KAAK,UAAU,QAAQ,CAAC,IAAe;IACrC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,KAAK;YACpB,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,oCAAoC;SAC7C,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,OAAO;QACL,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,IAAI;QACb,OAAO;QACP,sEAAsE;QACtE,2EAA2E;QAC3E,oDAAoD;QACpD,aAAa,EAAE,KAAK;QACpB,QAAQ;QACR,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,QAAQ;YACd,CAAC,CAAC,OAAO,OAAO,IAAI,GAAG,kBAAkB,QAAQ,EAAE;YACnD,CAAC,CAAC,OAAO,OAAO,IAAI,GAAG,6BAA6B;KACvD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,IAAe;IAC1D,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,IAAI,OAAO,GAAgC,MAAM,CAAC;IAClD,IAAI,QAAQ,GAAG,sBAAsB,CAAC;QACpC,QAAQ;QACR,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,UAAU,QAAQ,yCAAyC;KACpE,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACvB,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC1B,QAAQ,GAAG,MAAM,CAAC,eAAe;YAC/B,CAAC,CAAC,MAAM,MAAM,CAAC,eAAe,EAAE;YAChC,CAAC,CAAC,sBAAsB,CAAC;gBACrB,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,OAAO,EAAE,MAAM,CAAC,QAAQ;gBACxB,MAAM,EAAE,gDAAgD;aACzD,CAAC,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,mDAAmD;IACrD,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,CAAC,CAAC;IACnC,OAAO;QACL,SAAS,EAAE,QAAQ;QACnB,OAAO;QACP,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI;QAClD,2EAA2E;QAC3E,6EAA6E;QAC7E,sCAAsC;QACtC,SAAS,EAAE,QAAQ,CAAC,aAAa,KAAK,IAAI;QAC1C,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,OAAO;QACpC,aAAa,EAAE,QAAQ,CAAC,aAAa;QACrC,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;QAC7C,MAAM,EAAE,OAAO;YACb,CAAC,CAAC,QAAQ,CAAC,MAAM;YACjB,CAAC,CAAC,GAAG,QAAQ,oCAAoC,QAAQ,CAAC,MAAM,EAAE;KACrE,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAmC,EAAE;IAErC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC;IACtC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,aAAa,CAAC,EAAE,CAAC;IAEtD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAC3E,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEhF,OAAO,gBAAgB,CAAC;QACtB,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,cAAc;QACrE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ;QAC3C,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI;QAC/B,cAAc,EAAE,IAAI,CAAC,aAAa,IAAI,eAAe;QACrD,KAAK;QACL,OAAO;KACR,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type EngineCapability, type MachineCapabilitySnapshot, type ToolCapability } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Credential SHAPES a developer machine actually holds, plus the two generic
|
|
4
|
+
* carriers. Mirrors the regexes in `acc.machine_capability_material_free`
|
|
5
|
+
* (0310 §2); `tests/unit/t1785160000000035-machine-capabilities.test.ts` asserts
|
|
6
|
+
* the mirror by running the same vectors through both spellings.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CREDENTIAL_SHAPES: readonly RegExp[];
|
|
9
|
+
/** True when `value` contains anything shaped like a credential. */
|
|
10
|
+
export declare function looksLikeCredential(value: string | null | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Mask + bound one free-text field. Applies `maskGitCredentials` first (the
|
|
13
|
+
* GIT-AUTH-ALERT masker, so URL userinfo and bare gh tokens are rewritten rather
|
|
14
|
+
* than dropped), then DROPS the value entirely if a credential shape survived —
|
|
15
|
+
* an unmaskable value is not worth a field. Length-bounded so a whitelisted
|
|
16
|
+
* field cannot become the smuggling channel.
|
|
17
|
+
*/
|
|
18
|
+
export declare function safeText(value: string | null | undefined, maxLength: number): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Bound, dedupe and sort gh scope labels. Sorted so two snapshots of the same
|
|
21
|
+
* machine compare equal regardless of the order gh printed them, which is what
|
|
22
|
+
* makes the "did anything change?" fingerprint in report.ts stable. A label that
|
|
23
|
+
* looks like a credential is dropped rather than masked — a scope label is a
|
|
24
|
+
* short well-known word, so an unmaskable one is not a scope.
|
|
25
|
+
*/
|
|
26
|
+
export declare function safeScopes(scopes: readonly string[] | null | undefined): string[];
|
|
27
|
+
/** Rebuild one tool row from the closed field set. */
|
|
28
|
+
export declare function sanitizeTool(tool: ToolCapability): ToolCapability;
|
|
29
|
+
/** Rebuild one engine row from the closed field set. */
|
|
30
|
+
export declare function sanitizeEngine(engine: EngineCapability): EngineCapability;
|
|
31
|
+
/**
|
|
32
|
+
* Rebuild a whole snapshot. The ONLY function the reporter is allowed to send
|
|
33
|
+
* the output of — `recordMachineCapabilities` calls it unconditionally, even on a
|
|
34
|
+
* snapshot this package built itself, because the point is that no future probe
|
|
35
|
+
* can add a field that bypasses it.
|
|
36
|
+
*
|
|
37
|
+
* Rows for tools outside `HOST_TOOLS` and engines with a blank id are dropped:
|
|
38
|
+
* the server-side whitelist would drop them anyway, and sending them would make
|
|
39
|
+
* the local snapshot disagree with the recorded one.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sanitizeSnapshot(snapshot: MachineCapabilitySnapshot): MachineCapabilitySnapshot;
|
|
42
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/machine-capabilities/redact.ts"],"names":[],"mappings":"AA6BA,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAkB9C,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAG7E;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAMf;AAOD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAWjF;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CAWjE;AAED,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAYzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,yBAAyB,GAClC,yBAAyB,CAgB3B"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AX-H1 (MACHINE-CAPABILITIES): the LAST THING that runs on the host before a
|
|
3
|
+
* capability snapshot leaves it.
|
|
4
|
+
*
|
|
5
|
+
* AUTH_APPROVAL_BRIEF.md §7.1 / the MLSA plan: no credential ever transits ACC —
|
|
6
|
+
* presence and identity only. That is enforced in three independent places, and
|
|
7
|
+
* this is the first of them:
|
|
8
|
+
*
|
|
9
|
+
* 1. HERE — `sanitizeSnapshot` REBUILDS every record from the closed field set
|
|
10
|
+
* in types.ts and masks every free-text field, so nothing the probe happened
|
|
11
|
+
* to capture can ride along. A rebuild rather than a filter: a field nobody
|
|
12
|
+
* whitelisted is absent from the output by construction, not removed by a
|
|
13
|
+
* rule somebody has to remember to write.
|
|
14
|
+
* 2. `acc._normalize_machine_capability_{tools,engines}` (migration 0310 §6)
|
|
15
|
+
* does the same rebuild server-side, so a rogue or stale runner build cannot
|
|
16
|
+
* widen the record.
|
|
17
|
+
* 3. Two CHECK constraints on `acc.machine_capabilities` reject anything SHAPED
|
|
18
|
+
* like a credential at rest, so material that somehow reached a whitelisted
|
|
19
|
+
* field is unstorable rather than merely unwritten.
|
|
20
|
+
*
|
|
21
|
+
* `looksLikeCredential` is the shared shape test behind (1) and (3) — the
|
|
22
|
+
* patterns are mirrored in `acc.machine_capability_material_free(jsonb)` and the
|
|
23
|
+
* unit suite asserts the two agree case by case. It is a SHAPE test, not entropy
|
|
24
|
+
* analysis: it cannot prove a string is not a secret, which is exactly why it is
|
|
25
|
+
* the backstop and the rebuild is the boundary.
|
|
26
|
+
*
|
|
27
|
+
* PURE by construction — no I/O, no clock — so every rule is unit-testable.
|
|
28
|
+
*/
|
|
29
|
+
import { maskGitCredentials } from "../git-auth/probe.js";
|
|
30
|
+
import { HOST_TOOLS, } from "./types.js";
|
|
31
|
+
/**
|
|
32
|
+
* Credential SHAPES a developer machine actually holds, plus the two generic
|
|
33
|
+
* carriers. Mirrors the regexes in `acc.machine_capability_material_free`
|
|
34
|
+
* (0310 §2); `tests/unit/t1785160000000035-machine-capabilities.test.ts` asserts
|
|
35
|
+
* the mirror by running the same vectors through both spellings.
|
|
36
|
+
*/
|
|
37
|
+
export const CREDENTIAL_SHAPES = [
|
|
38
|
+
// GitHub personal / OAuth / app / refresh tokens, and fine-grained PATs.
|
|
39
|
+
/gh[posru]_[A-Za-z0-9]{16,}/,
|
|
40
|
+
/github_pat_[A-Za-z0-9_]{20,}/,
|
|
41
|
+
// Anthropic, OpenAI-family, Google AI keys.
|
|
42
|
+
/sk-ant-[A-Za-z0-9_-]{16,}/,
|
|
43
|
+
/sk-[A-Za-z0-9]{32,}/,
|
|
44
|
+
/AIza[0-9A-Za-z_-]{30,}/,
|
|
45
|
+
// Slack bot/user/app tokens (an MCP or notifier credential).
|
|
46
|
+
/xox[baprse]-[A-Za-z0-9-]{10,}/,
|
|
47
|
+
// A JWT (a Supabase service-role key, an ACC-signed runner token).
|
|
48
|
+
/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\./,
|
|
49
|
+
// A PEM private key block (a GitHub App key).
|
|
50
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
51
|
+
// userinfo in a URL: scheme://user:token@host — how a PAT leaks out of a git
|
|
52
|
+
// remote. maskGitCredentials rewrites this to scheme://***@host, which has no
|
|
53
|
+
// colon before the '@' and therefore does not match.
|
|
54
|
+
/:\/\/[^/\s"]+:[^/\s"@]+@/,
|
|
55
|
+
];
|
|
56
|
+
/** True when `value` contains anything shaped like a credential. */
|
|
57
|
+
export function looksLikeCredential(value) {
|
|
58
|
+
if (!value)
|
|
59
|
+
return false;
|
|
60
|
+
return CREDENTIAL_SHAPES.some((re) => re.test(value));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Mask + bound one free-text field. Applies `maskGitCredentials` first (the
|
|
64
|
+
* GIT-AUTH-ALERT masker, so URL userinfo and bare gh tokens are rewritten rather
|
|
65
|
+
* than dropped), then DROPS the value entirely if a credential shape survived —
|
|
66
|
+
* an unmaskable value is not worth a field. Length-bounded so a whitelisted
|
|
67
|
+
* field cannot become the smuggling channel.
|
|
68
|
+
*/
|
|
69
|
+
export function safeText(value, maxLength) {
|
|
70
|
+
if (value === null || value === undefined)
|
|
71
|
+
return null;
|
|
72
|
+
const masked = maskGitCredentials(String(value)).trim();
|
|
73
|
+
if (!masked)
|
|
74
|
+
return null;
|
|
75
|
+
if (looksLikeCredential(masked))
|
|
76
|
+
return null;
|
|
77
|
+
return masked.slice(0, maxLength);
|
|
78
|
+
}
|
|
79
|
+
/** `safeText` for a field that must be a string: an unusable value becomes "". */
|
|
80
|
+
function safeDetail(value, maxLength = 300) {
|
|
81
|
+
return safeText(value, maxLength) ?? "";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Bound, dedupe and sort gh scope labels. Sorted so two snapshots of the same
|
|
85
|
+
* machine compare equal regardless of the order gh printed them, which is what
|
|
86
|
+
* makes the "did anything change?" fingerprint in report.ts stable. A label that
|
|
87
|
+
* looks like a credential is dropped rather than masked — a scope label is a
|
|
88
|
+
* short well-known word, so an unmaskable one is not a scope.
|
|
89
|
+
*/
|
|
90
|
+
export function safeScopes(scopes) {
|
|
91
|
+
if (!Array.isArray(scopes))
|
|
92
|
+
return [];
|
|
93
|
+
const out = new Set();
|
|
94
|
+
for (const raw of scopes) {
|
|
95
|
+
if (typeof raw !== "string")
|
|
96
|
+
continue;
|
|
97
|
+
const label = raw.trim();
|
|
98
|
+
if (!label || looksLikeCredential(label))
|
|
99
|
+
continue;
|
|
100
|
+
out.add(label.slice(0, 60));
|
|
101
|
+
if (out.size >= 32)
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
return [...out].sort();
|
|
105
|
+
}
|
|
106
|
+
/** Rebuild one tool row from the closed field set. */
|
|
107
|
+
export function sanitizeTool(tool) {
|
|
108
|
+
return {
|
|
109
|
+
tool: tool.tool,
|
|
110
|
+
present: tool.present === true,
|
|
111
|
+
version: safeText(tool.version, 60),
|
|
112
|
+
authenticated: tool.authenticated === true,
|
|
113
|
+
identity: safeText(tool.identity, 200),
|
|
114
|
+
host: safeText(tool.host, 120),
|
|
115
|
+
scopes: safeScopes(tool.scopes),
|
|
116
|
+
detail: safeDetail(tool.detail),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Rebuild one engine row from the closed field set. */
|
|
120
|
+
export function sanitizeEngine(engine) {
|
|
121
|
+
return {
|
|
122
|
+
engine_id: (engine.engine_id ?? "").trim().slice(0, 60),
|
|
123
|
+
present: engine.present === true,
|
|
124
|
+
version: safeText(engine.version, 60),
|
|
125
|
+
logged_in: engine.logged_in === true,
|
|
126
|
+
channel: engine.channel,
|
|
127
|
+
account_email: safeText(engine.account_email, 200),
|
|
128
|
+
plan_hint: engine.plan_hint,
|
|
129
|
+
synthetic_account: safeDetail(engine.synthetic_account, 260),
|
|
130
|
+
detail: safeDetail(engine.detail),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Rebuild a whole snapshot. The ONLY function the reporter is allowed to send
|
|
135
|
+
* the output of — `recordMachineCapabilities` calls it unconditionally, even on a
|
|
136
|
+
* snapshot this package built itself, because the point is that no future probe
|
|
137
|
+
* can add a field that bypasses it.
|
|
138
|
+
*
|
|
139
|
+
* Rows for tools outside `HOST_TOOLS` and engines with a blank id are dropped:
|
|
140
|
+
* the server-side whitelist would drop them anyway, and sending them would make
|
|
141
|
+
* the local snapshot disagree with the recorded one.
|
|
142
|
+
*/
|
|
143
|
+
export function sanitizeSnapshot(snapshot) {
|
|
144
|
+
const known = new Set(HOST_TOOLS);
|
|
145
|
+
return {
|
|
146
|
+
machine: safeDetail(snapshot.machine, 200) || "unknown-host",
|
|
147
|
+
platform: safeDetail(snapshot.platform, 40) || "unknown",
|
|
148
|
+
arch: safeDetail(snapshot.arch, 40) || "unknown",
|
|
149
|
+
runner_version: safeDetail(snapshot.runner_version, 60),
|
|
150
|
+
tools: (snapshot.tools ?? [])
|
|
151
|
+
.filter((t) => known.has(t?.tool))
|
|
152
|
+
.slice(0, 16)
|
|
153
|
+
.map(sanitizeTool),
|
|
154
|
+
engines: (snapshot.engines ?? [])
|
|
155
|
+
.filter((e) => typeof e?.engine_id === "string" && e.engine_id.trim().length > 0)
|
|
156
|
+
.slice(0, 32)
|
|
157
|
+
.map(sanitizeEngine),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/machine-capabilities/redact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { MachineCapabilityReport, MachineCapabilitySnapshot } from "./types.js";
|
|
2
|
+
/** Minimum gap between host probes on the shared AS-1 cadence. */
|
|
3
|
+
export declare const DEFAULT_PROBE_MIN_INTERVAL_MS: number;
|
|
4
|
+
/** Minimum gap between re-recording an UNCHANGED snapshot (liveness refresh). */
|
|
5
|
+
export declare const DEFAULT_REFRESH_MIN_INTERVAL_MS: number;
|
|
6
|
+
/** The RPC surface the reporter needs — a supabase-js client satisfies it. */
|
|
7
|
+
export interface MachineCapabilityTransport {
|
|
8
|
+
rpc(fn: string, args: Record<string, unknown>): PromiseLike<{
|
|
9
|
+
data?: unknown;
|
|
10
|
+
error?: {
|
|
11
|
+
message: string;
|
|
12
|
+
} | null;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A stable content fingerprint of a snapshot. Deliberately excludes nothing:
|
|
17
|
+
* every field that reaches the server is in it, so "unchanged" means the recorded
|
|
18
|
+
* row would be byte-identical. Sorted keys, so JSON key order cannot make an
|
|
19
|
+
* unchanged machine look changed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function snapshotFingerprint(snapshot: MachineCapabilitySnapshot): string;
|
|
22
|
+
export interface MachineCapabilityReporterDeps {
|
|
23
|
+
/** Probe the host. Should not throw; a throw is caught and logged. */
|
|
24
|
+
probe: () => Promise<MachineCapabilitySnapshot>;
|
|
25
|
+
/** The RPC transport. Omit to make the reporter probe-only (tests / dry runs). */
|
|
26
|
+
transport?: MachineCapabilityTransport | null;
|
|
27
|
+
/** The runner id to attribute the report to, when this process is a runner. */
|
|
28
|
+
runnerId?: string | null;
|
|
29
|
+
/** Min gap between host probes. Default 15 min. */
|
|
30
|
+
probeMinIntervalMs?: number;
|
|
31
|
+
/** Min gap between re-recording an unchanged snapshot. Default 6 h. */
|
|
32
|
+
refreshMinIntervalMs?: number;
|
|
33
|
+
/** Clock (test seam). Default Date.now. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
/** Best-effort log sink. */
|
|
36
|
+
onLog?: (line: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/** What one cadence tick did, so a caller (and a test) can assert the gates. */
|
|
39
|
+
export type MachineCapabilityTickOutcome = {
|
|
40
|
+
action: "skipped";
|
|
41
|
+
reason: "throttled";
|
|
42
|
+
} | {
|
|
43
|
+
action: "probed";
|
|
44
|
+
recorded: boolean;
|
|
45
|
+
changed: boolean;
|
|
46
|
+
/** Why the write happened (or didn't): changed | liveness-refresh | unchanged | no-transport. */
|
|
47
|
+
reason: string;
|
|
48
|
+
/** Why the PROBE happened: first-probe | account-changed | interval-elapsed. */
|
|
49
|
+
probeReason: string;
|
|
50
|
+
} | {
|
|
51
|
+
action: "failed";
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Drives the capability probe on somebody else's cadence. Holds only the last
|
|
56
|
+
* fingerprint + two timestamps, so it is cheap to keep for the process lifetime
|
|
57
|
+
* and has no timer of its own — which is what makes "there is exactly one probe
|
|
58
|
+
* loop on this host" structurally true rather than a claim in a comment.
|
|
59
|
+
*/
|
|
60
|
+
export declare class MachineCapabilityReporter {
|
|
61
|
+
private readonly deps;
|
|
62
|
+
private readonly probeMinIntervalMs;
|
|
63
|
+
private readonly refreshMinIntervalMs;
|
|
64
|
+
private readonly now;
|
|
65
|
+
private lastProbeAt;
|
|
66
|
+
private lastRecordAt;
|
|
67
|
+
private lastFingerprint;
|
|
68
|
+
private lastAccountKey;
|
|
69
|
+
private seq;
|
|
70
|
+
/** Serial chain so two triggers never probe concurrently or record out of order. */
|
|
71
|
+
private tail;
|
|
72
|
+
constructor(deps: MachineCapabilityReporterDeps);
|
|
73
|
+
/** The last recorded fingerprint (null before the first successful probe). */
|
|
74
|
+
fingerprint(): string | null;
|
|
75
|
+
/** Resolve once every queued tick has settled (test seam). */
|
|
76
|
+
settle(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Note one AS-1 cadence tick. `accountKey` is the AS-1 `synthetic_account` of
|
|
79
|
+
* the identity that tick resolved: when it moves, the engine rows are stale by
|
|
80
|
+
* definition and the throttle is bypassed.
|
|
81
|
+
*
|
|
82
|
+
* Returns the outcome and never rejects — a capability report is
|
|
83
|
+
* best-effort telemetry and must never be able to fail a cadence tick.
|
|
84
|
+
*/
|
|
85
|
+
onProbeTick(args: {
|
|
86
|
+
trigger: string;
|
|
87
|
+
accountKey?: string | null;
|
|
88
|
+
}): Promise<MachineCapabilityTickOutcome>;
|
|
89
|
+
private shouldProbe;
|
|
90
|
+
private tick;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Send one report to `acc.record_machine_capabilities`. Returns false (and logs)
|
|
94
|
+
* on any failure rather than throwing: a pre-0310 server 404s this RPC, and an
|
|
95
|
+
* operator on an older deploy must not have their runner wedged by a telemetry
|
|
96
|
+
* write — the same non-fatal contract `set_runner_capabilities` uses in watch.ts.
|
|
97
|
+
*
|
|
98
|
+
* The snapshot is sanitized ONE more time on the way out. That is not paranoia
|
|
99
|
+
* theatre: this function is exported and callable with an arbitrary snapshot, and
|
|
100
|
+
* the invariant it has to hold is "nothing that leaves this process carries
|
|
101
|
+
* credential material", not "nothing that leaves this class does".
|
|
102
|
+
*/
|
|
103
|
+
export declare function recordMachineCapabilities(transport: MachineCapabilityTransport, report: MachineCapabilityReport, opts?: {
|
|
104
|
+
runnerId?: string | null;
|
|
105
|
+
onLog?: (line: string) => void;
|
|
106
|
+
}): Promise<boolean>;
|
|
107
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/machine-capabilities/report.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAErF,kEAAkE;AAClE,eAAO,MAAM,6BAA6B,QAAc,CAAC;AACzD,iFAAiF;AACjF,eAAO,MAAM,+BAA+B,QAAkB,CAAC;AAE/D,8EAA8E;AAC9E,MAAM,WAAW,0BAA0B;IACzC,GAAG,CACD,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,WAAW,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CACxE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,MAAM,CAQ/E;AAED,MAAM,WAAW,6BAA6B;IAC5C,sEAAsE;IACtE,KAAK,EAAE,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAChD,kFAAkF;IAClF,SAAS,CAAC,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC9C,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,gFAAgF;AAChF,MAAM,MAAM,4BAA4B,GACpC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAC1C;IACE,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;CACrB,GACD;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;GAKG;AACH,qBAAa,yBAAyB;IAaxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAZjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,GAAG,CAAK;IAChB,oFAAoF;IACpF,OAAO,CAAC,IAAI,CAAoC;gBAEnB,IAAI,EAAE,6BAA6B;IAShE,8EAA8E;IAC9E,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,8DAA8D;IACxD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;;;;;OAOG;IACH,WAAW,CAAC,IAAI,EAAE;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAUzC,OAAO,CAAC,WAAW;YAUL,IAAI;CA4EnB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,0BAA0B,EACrC,MAAM,EAAE,uBAAuB,EAC/B,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GACtE,OAAO,CAAC,OAAO,CAAC,CA+BlB"}
|