@tokenfactory/acc-runner 0.42.0 → 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.
Files changed (68) hide show
  1. package/dist/engines/account-probe.d.ts +24 -0
  2. package/dist/engines/account-probe.d.ts.map +1 -1
  3. package/dist/engines/account-probe.js +18 -1
  4. package/dist/engines/account-probe.js.map +1 -1
  5. package/dist/machine-capabilities/index.d.ts +21 -0
  6. package/dist/machine-capabilities/index.d.ts.map +1 -0
  7. package/dist/machine-capabilities/index.js +21 -0
  8. package/dist/machine-capabilities/index.js.map +1 -0
  9. package/dist/machine-capabilities/probe.d.ts +61 -0
  10. package/dist/machine-capabilities/probe.d.ts.map +1 -0
  11. package/dist/machine-capabilities/probe.js +243 -0
  12. package/dist/machine-capabilities/probe.js.map +1 -0
  13. package/dist/machine-capabilities/redact.d.ts +42 -0
  14. package/dist/machine-capabilities/redact.d.ts.map +1 -0
  15. package/dist/machine-capabilities/redact.js +160 -0
  16. package/dist/machine-capabilities/redact.js.map +1 -0
  17. package/dist/machine-capabilities/report.d.ts +107 -0
  18. package/dist/machine-capabilities/report.d.ts.map +1 -0
  19. package/dist/machine-capabilities/report.js +211 -0
  20. package/dist/machine-capabilities/report.js.map +1 -0
  21. package/dist/machine-capabilities/types.d.ts +115 -0
  22. package/dist/machine-capabilities/types.d.ts.map +1 -0
  23. package/dist/machine-capabilities/types.js +11 -0
  24. package/dist/machine-capabilities/types.js.map +1 -0
  25. package/dist/migration-seq-guard.d.ts +78 -0
  26. package/dist/migration-seq-guard.d.ts.map +1 -0
  27. package/dist/migration-seq-guard.js +304 -0
  28. package/dist/migration-seq-guard.js.map +1 -0
  29. package/dist/node-version.d.ts +132 -0
  30. package/dist/node-version.d.ts.map +1 -0
  31. package/dist/node-version.js +318 -0
  32. package/dist/node-version.js.map +1 -0
  33. package/dist/prepr-gate.d.ts +120 -0
  34. package/dist/prepr-gate.d.ts.map +1 -1
  35. package/dist/prepr-gate.js +202 -3
  36. package/dist/prepr-gate.js.map +1 -1
  37. package/dist/program.d.ts.map +1 -1
  38. package/dist/program.js +13 -0
  39. package/dist/program.js.map +1 -1
  40. package/dist/runtime/review-chunking.d.ts +101 -0
  41. package/dist/runtime/review-chunking.d.ts.map +1 -0
  42. package/dist/runtime/review-chunking.js +192 -0
  43. package/dist/runtime/review-chunking.js.map +1 -0
  44. package/dist/runtime/review-diff.d.ts +30 -0
  45. package/dist/runtime/review-diff.d.ts.map +1 -0
  46. package/dist/runtime/review-diff.js +59 -0
  47. package/dist/runtime/review-diff.js.map +1 -0
  48. package/dist/runtime/reviewer.d.ts +40 -19
  49. package/dist/runtime/reviewer.d.ts.map +1 -1
  50. package/dist/runtime/reviewer.js +252 -51
  51. package/dist/runtime/reviewer.js.map +1 -1
  52. package/dist/serve-scope/command.d.ts +12 -0
  53. package/dist/serve-scope/command.d.ts.map +1 -0
  54. package/dist/serve-scope/command.js +89 -0
  55. package/dist/serve-scope/command.js.map +1 -0
  56. package/dist/serve-scope/index.d.ts +130 -0
  57. package/dist/serve-scope/index.d.ts.map +1 -0
  58. package/dist/serve-scope/index.js +148 -0
  59. package/dist/serve-scope/index.js.map +1 -0
  60. package/dist/task-runner.d.ts +11 -0
  61. package/dist/task-runner.d.ts.map +1 -1
  62. package/dist/task-runner.js +69 -0
  63. package/dist/task-runner.js.map +1 -1
  64. package/dist/watch.d.ts +8 -0
  65. package/dist/watch.d.ts.map +1 -1
  66. package/dist/watch.js +28 -0
  67. package/dist/watch.js.map +1 -1
  68. package/package.json +1 -1
@@ -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":"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"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * AX-H1 (MACHINE-CAPABILITIES): the reporter — decide WHETHER to probe on this
3
+ * AS-1 cadence tick, and record the result.
4
+ *
5
+ * WHY A GATE AND NOT JUST "PROBE EVERY TICK". AX-H1 rides the AS-1 cadence
6
+ * (account-probe.ts) rather than adding a loop of its own, and that cadence ticks
7
+ * every ~60s while the runner is capacity-paused. An account probe is a config
8
+ * read; a capability probe shells out to gh, git and every engine — four to six
9
+ * subprocesses. Firing that every minute forever would make a cheap loop
10
+ * expensive and would rewrite an unchanged row all day.
11
+ *
12
+ * So the gate has three rules, in priority order:
13
+ *
14
+ * 1. FIRST TICK always probes. A machine that never reports is a machine ACC
15
+ * cannot describe, which is the whole gap.
16
+ * 2. AN ACCOUNT CHANGE always probes. A login swap is precisely when the engine
17
+ * rows went stale, and it is the AS-1 signal that already exists.
18
+ * 3. Otherwise probe at most once per `probeMinIntervalMs` (default 15 min).
19
+ *
20
+ * …and the WRITE has one more: an unchanged snapshot is only re-recorded once per
21
+ * `refreshMinIntervalMs` (default 6 h), so `probed_at` still proves liveness
22
+ * without an unchanged row being written 96 times a day. `snapshotFingerprint`
23
+ * decides "unchanged" over the whole sanitized document, so a version bump or a
24
+ * lost gh login is a change and a reordered scope list is not.
25
+ *
26
+ * FULLY injectable (probe / rpc / clock) so both gates are asserted with a fake
27
+ * clock and zero subprocesses.
28
+ */
29
+ import { sanitizeSnapshot } from "./redact.js";
30
+ /** Minimum gap between host probes on the shared AS-1 cadence. */
31
+ export const DEFAULT_PROBE_MIN_INTERVAL_MS = 15 * 60_000;
32
+ /** Minimum gap between re-recording an UNCHANGED snapshot (liveness refresh). */
33
+ export const DEFAULT_REFRESH_MIN_INTERVAL_MS = 6 * 60 * 60_000;
34
+ /**
35
+ * A stable content fingerprint of a snapshot. Deliberately excludes nothing:
36
+ * every field that reaches the server is in it, so "unchanged" means the recorded
37
+ * row would be byte-identical. Sorted keys, so JSON key order cannot make an
38
+ * unchanged machine look changed.
39
+ */
40
+ export function snapshotFingerprint(snapshot) {
41
+ return JSON.stringify(snapshot, (_key, value) => {
42
+ if (value && typeof value === "object" && !Array.isArray(value)) {
43
+ const obj = value;
44
+ return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, obj[k]]));
45
+ }
46
+ return value;
47
+ });
48
+ }
49
+ /**
50
+ * Drives the capability probe on somebody else's cadence. Holds only the last
51
+ * fingerprint + two timestamps, so it is cheap to keep for the process lifetime
52
+ * and has no timer of its own — which is what makes "there is exactly one probe
53
+ * loop on this host" structurally true rather than a claim in a comment.
54
+ */
55
+ export class MachineCapabilityReporter {
56
+ deps;
57
+ probeMinIntervalMs;
58
+ refreshMinIntervalMs;
59
+ now;
60
+ lastProbeAt = null;
61
+ lastRecordAt = null;
62
+ lastFingerprint = null;
63
+ lastAccountKey = null;
64
+ seq = 0;
65
+ /** Serial chain so two triggers never probe concurrently or record out of order. */
66
+ tail = Promise.resolve();
67
+ constructor(deps) {
68
+ this.deps = deps;
69
+ this.probeMinIntervalMs = Math.max(0, deps.probeMinIntervalMs ?? DEFAULT_PROBE_MIN_INTERVAL_MS);
70
+ this.refreshMinIntervalMs = Math.max(0, deps.refreshMinIntervalMs ?? DEFAULT_REFRESH_MIN_INTERVAL_MS);
71
+ this.now = deps.now ?? Date.now;
72
+ }
73
+ /** The last recorded fingerprint (null before the first successful probe). */
74
+ fingerprint() {
75
+ return this.lastFingerprint;
76
+ }
77
+ /** Resolve once every queued tick has settled (test seam). */
78
+ async settle() {
79
+ await this.tail;
80
+ }
81
+ /**
82
+ * Note one AS-1 cadence tick. `accountKey` is the AS-1 `synthetic_account` of
83
+ * the identity that tick resolved: when it moves, the engine rows are stale by
84
+ * definition and the throttle is bypassed.
85
+ *
86
+ * Returns the outcome and never rejects — a capability report is
87
+ * best-effort telemetry and must never be able to fail a cadence tick.
88
+ */
89
+ onProbeTick(args) {
90
+ const run = this.tail.then(() => this.tick(args));
91
+ // Keep the chain alive even if a tick rejects (it should not — tick catches).
92
+ this.tail = run.then(() => undefined, () => undefined);
93
+ return run;
94
+ }
95
+ shouldProbe(accountKey) {
96
+ if (this.lastProbeAt === null)
97
+ return { probe: true, reason: "first-probe" };
98
+ if (accountKey !== null && accountKey !== this.lastAccountKey) {
99
+ return { probe: true, reason: "account-changed" };
100
+ }
101
+ const age = this.now() - this.lastProbeAt;
102
+ if (age >= this.probeMinIntervalMs)
103
+ return { probe: true, reason: "interval-elapsed" };
104
+ return { probe: false, reason: "throttled" };
105
+ }
106
+ async tick(args) {
107
+ const accountKey = args.accountKey ?? null;
108
+ const decision = this.shouldProbe(accountKey);
109
+ // A skip is only ever reached when the account is UNCHANGED (rule 2 above
110
+ // outranks the throttle), so nothing is lost by not tracking the key here.
111
+ if (!decision.probe) {
112
+ return { action: "skipped", reason: "throttled" };
113
+ }
114
+ let snapshot;
115
+ try {
116
+ // Sanitized again here even though probe.ts already sanitizes: this class
117
+ // accepts an INJECTED probe, and the redaction boundary must not depend on
118
+ // which probe was injected.
119
+ snapshot = sanitizeSnapshot(await this.deps.probe());
120
+ }
121
+ catch (err) {
122
+ this.deps.onLog?.(`[acc-runner] machine-capability probe (${args.trigger}) failed: ${err?.message ?? String(err)}`);
123
+ return { action: "failed", reason: "probe-threw" };
124
+ }
125
+ const at = this.now();
126
+ this.lastProbeAt = at;
127
+ if (accountKey !== null)
128
+ this.lastAccountKey = accountKey;
129
+ const fingerprint = snapshotFingerprint(snapshot);
130
+ const changed = fingerprint !== this.lastFingerprint;
131
+ const refreshDue = this.lastRecordAt === null || at - this.lastRecordAt >= this.refreshMinIntervalMs;
132
+ if (!changed && !refreshDue) {
133
+ return {
134
+ action: "probed",
135
+ recorded: false,
136
+ changed: false,
137
+ reason: "unchanged",
138
+ probeReason: decision.reason,
139
+ };
140
+ }
141
+ if (!this.deps.transport) {
142
+ this.lastFingerprint = fingerprint;
143
+ return {
144
+ action: "probed",
145
+ recorded: false,
146
+ changed,
147
+ reason: "no-transport",
148
+ probeReason: decision.reason,
149
+ };
150
+ }
151
+ const report = {
152
+ snapshot,
153
+ trigger: args.trigger,
154
+ seq: ++this.seq,
155
+ };
156
+ const recorded = await recordMachineCapabilities(this.deps.transport, report, {
157
+ runnerId: this.deps.runnerId ?? null,
158
+ onLog: this.deps.onLog,
159
+ });
160
+ if (recorded) {
161
+ this.lastRecordAt = at;
162
+ this.lastFingerprint = fingerprint;
163
+ }
164
+ return {
165
+ action: "probed",
166
+ recorded,
167
+ changed,
168
+ reason: changed ? "changed" : "liveness-refresh",
169
+ probeReason: decision.reason,
170
+ };
171
+ }
172
+ }
173
+ /**
174
+ * Send one report to `acc.record_machine_capabilities`. Returns false (and logs)
175
+ * on any failure rather than throwing: a pre-0310 server 404s this RPC, and an
176
+ * operator on an older deploy must not have their runner wedged by a telemetry
177
+ * write — the same non-fatal contract `set_runner_capabilities` uses in watch.ts.
178
+ *
179
+ * The snapshot is sanitized ONE more time on the way out. That is not paranoia
180
+ * theatre: this function is exported and callable with an arbitrary snapshot, and
181
+ * the invariant it has to hold is "nothing that leaves this process carries
182
+ * credential material", not "nothing that leaves this class does".
183
+ */
184
+ export async function recordMachineCapabilities(transport, report, opts = {}) {
185
+ const snapshot = sanitizeSnapshot(report.snapshot);
186
+ try {
187
+ const { error } = await transport.rpc("record_machine_capabilities", {
188
+ p_machine: snapshot.machine,
189
+ p_runner_id: opts.runnerId ?? null,
190
+ p_platform: snapshot.platform,
191
+ p_arch: snapshot.arch,
192
+ p_runner_version: snapshot.runner_version || null,
193
+ p_tools: snapshot.tools,
194
+ p_engines: snapshot.engines,
195
+ p_probe_trigger: report.trigger,
196
+ p_probe_seq: report.seq,
197
+ // No timestamp argument by design: probed_at is server time (0310 §4), so a
198
+ // host with a wrong clock cannot win last-writer-wins forever.
199
+ });
200
+ if (error) {
201
+ opts.onLog?.(`[acc-runner] record_machine_capabilities failed (continuing): ${error.message}`);
202
+ return false;
203
+ }
204
+ return true;
205
+ }
206
+ catch (err) {
207
+ opts.onLog?.(`[acc-runner] record_machine_capabilities threw (continuing): ${err?.message ?? String(err)}`);
208
+ return false;
209
+ }
210
+ }
211
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/machine-capabilities/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * AX-H1 (MACHINE-CAPABILITIES): the shapes a host reports about ITSELF.
3
+ *
4
+ * The one rule these types exist to encode (AUTH_APPROVAL_BRIEF.md §7.1 + the
5
+ * MLSA plan): NO CREDENTIAL EVER TRANSITS ACC. Every field below is either
6
+ *
7
+ * - PRESENCE — is the tool installed, does it authenticate, which version;
8
+ * - IDENTITY — which login / email / plan / scope-set it resolves to;
9
+ * - PROVENANCE — which host, which runner, which AS-1 probe trigger.
10
+ *
11
+ * There is deliberately no field that could hold a token, and the field set is
12
+ * CLOSED on three sides: `sanitizeSnapshot` (redact.ts) rebuilds every record
13
+ * from these fields before it leaves the host,
14
+ * `acc._normalize_machine_capability_{tools,engines}` (migration 0310) rebuilds
15
+ * it again from the same whitelist server-side, and two CHECK constraints reject
16
+ * anything shaped like a credential at rest. Adding a field here without adding
17
+ * it to BOTH whitelists means the field is silently dropped, which is the
18
+ * intended failure direction.
19
+ *
20
+ * DECLARATIONS ONLY — no behaviour, no I/O, so the contract can be asserted
21
+ * against the SQL mirror in a unit test.
22
+ */
23
+ import type { EngineAuthMode, EnginePlanHint } from "../engines/types.js";
24
+ /**
25
+ * The host tools ACC probes. Mirrors `acc.machine_capability_tools()` (0310 §1)
26
+ * and `HOST_TOOLS` in api/_lib/machine-capabilities.ts — the unit suite asserts
27
+ * all three agree, so a tool added on one side cannot silently be dropped by
28
+ * the writer's whitelist on another.
29
+ *
30
+ * Deliberately NOT the `acc.runners.capabilities` vocabulary (0114/0115): those
31
+ * are routing tags an operator DECLARES; these are tools ACC PROBES.
32
+ */
33
+ export declare const HOST_TOOLS: readonly ["gh", "git"];
34
+ export type HostTool = (typeof HOST_TOOLS)[number];
35
+ /** What one host tool offers. Presence + identity only. */
36
+ export interface ToolCapability {
37
+ /** Which tool this row describes. */
38
+ tool: HostTool;
39
+ /** The binary resolved on the (launchd-augmented) PATH. */
40
+ present: boolean;
41
+ /** Reported version, e.g. "2.62.0". Null when absent or unparseable. */
42
+ version: string | null;
43
+ /**
44
+ * The tool holds a working credential. For `gh` this is `gh auth status`
45
+ * exiting 0; `git` has no host-level login of its own, so it reports false.
46
+ */
47
+ authenticated: boolean;
48
+ /**
49
+ * WHO the tool acts as: the gh account login, or git's `user.email`. An
50
+ * identity, never a credential — and null rather than guessed when unknown.
51
+ */
52
+ identity: string | null;
53
+ /** The host the credential is for (gh: github.com / a GHES hostname). */
54
+ host: string | null;
55
+ /**
56
+ * The AUTHORITY LABELS a gh token carries ("repo", "read:org"). These are what
57
+ * make "can this machine open a PR" answerable. The token itself is never
58
+ * read: `gh auth status --show-token` is never invoked anywhere in this
59
+ * package, and the unit suite asserts that.
60
+ */
61
+ scopes: string[];
62
+ /** Masked, credential-safe one-line summary — safe to log, store and display. */
63
+ detail: string;
64
+ }
65
+ /** What one coding engine offers on this host. */
66
+ export interface EngineCapability {
67
+ /** Engine registry id (claude-code | codex | gemini). */
68
+ engine_id: string;
69
+ /** The engine's binary resolved on PATH. */
70
+ present: boolean;
71
+ /** Reported version. Null when absent or unparseable. */
72
+ version: string | null;
73
+ /** The engine resolved a real, named account (not the honest-unknown identity). */
74
+ logged_in: boolean;
75
+ /** Which auth channel the identity resolved through (AS-1 `channel`). */
76
+ channel: EngineAuthMode;
77
+ /** The account the engine is logged in as, or null when unintrospectable. */
78
+ account_email: string | null;
79
+ /** Coarse plan hint; "unknown" when the engine cannot detect one. */
80
+ plan_hint: EnginePlanHint;
81
+ /** The AS-1 stable synthetic account id. Always set. */
82
+ synthetic_account: string;
83
+ /** Masked, credential-safe one-line summary. */
84
+ detail: string;
85
+ }
86
+ /** Everything one host reports about itself in a single probe. */
87
+ export interface MachineCapabilitySnapshot {
88
+ /** os.hostname() — the operator-facing name of the machine. */
89
+ machine: string;
90
+ /** process.platform. */
91
+ platform: string;
92
+ /** process.arch. */
93
+ arch: string;
94
+ /** The acc-runner version that produced this snapshot. */
95
+ runner_version: string;
96
+ /** One row per probed host tool, in HOST_TOOLS order. */
97
+ tools: ToolCapability[];
98
+ /** One row per registered chat engine. */
99
+ engines: EngineCapability[];
100
+ }
101
+ /**
102
+ * A snapshot plus the AS-1 provenance of the probe that produced it. This is
103
+ * what `recordMachineCapabilities` sends to
104
+ * `acc.record_machine_capabilities` — note there is no timestamp: `probed_at` is
105
+ * server time, because a host with a wrong clock would otherwise win every
106
+ * last-writer-wins comparison forever (0310 §4).
107
+ */
108
+ export interface MachineCapabilityReport {
109
+ snapshot: MachineCapabilitySnapshot;
110
+ /** Which AS-1 cadence trigger fired this probe. */
111
+ trigger: string;
112
+ /** The probing process's monotonic probe sequence. */
113
+ seq: number;
114
+ }
115
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/machine-capabilities/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,wBAAyB,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,IAAI,EAAE,QAAQ,CAAC;IACf,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;OAGG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mFAAmF;IACnF,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,OAAO,EAAE,cAAc,CAAC;IACxB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qEAAqE;IACrE,SAAS,EAAE,cAAc,CAAC;IAC1B,wDAAwD;IACxD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kEAAkE;AAClE,MAAM,WAAW,yBAAyB;IACxC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,0CAA0C;IAC1C,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,yBAAyB,CAAC;IACpC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;CACb"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The host tools ACC probes. Mirrors `acc.machine_capability_tools()` (0310 §1)
3
+ * and `HOST_TOOLS` in api/_lib/machine-capabilities.ts — the unit suite asserts
4
+ * all three agree, so a tool added on one side cannot silently be dropped by
5
+ * the writer's whitelist on another.
6
+ *
7
+ * Deliberately NOT the `acc.runners.capabilities` vocabulary (0114/0115): those
8
+ * are routing tags an operator DECLARES; these are tools ACC PROBES.
9
+ */
10
+ export const HOST_TOOLS = ["gh", "git"];
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/machine-capabilities/types.ts"],"names":[],"mappings":"AAwBA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,KAAK,CAAU,CAAC"}