@tokenfactory/acc-runner 0.42.0 → 0.44.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 +52 -3
- package/dist/companion-run.d.ts.map +1 -1
- package/dist/companion-run.js +20 -0
- package/dist/companion-run.js.map +1 -1
- package/dist/config.d.ts +72 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +117 -3
- package/dist/config.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +13 -3
- package/dist/doctor.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/engines/registry.d.ts +17 -6
- package/dist/engines/registry.d.ts.map +1 -1
- package/dist/engines/registry.js +38 -13
- package/dist/engines/registry.js.map +1 -1
- package/dist/git-auth/github-token-source.d.ts +54 -0
- package/dist/git-auth/github-token-source.d.ts.map +1 -0
- package/dist/git-auth/github-token-source.js +44 -0
- package/dist/git-auth/github-token-source.js.map +1 -0
- package/dist/git-auth/index.d.ts +4 -0
- package/dist/git-auth/index.d.ts.map +1 -1
- package/dist/git-auth/index.js +4 -0
- package/dist/git-auth/index.js.map +1 -1
- package/dist/git-auth/installation-token.d.ts +95 -0
- package/dist/git-auth/installation-token.d.ts.map +1 -0
- package/dist/git-auth/installation-token.js +192 -0
- package/dist/git-auth/installation-token.js.map +1 -0
- package/dist/local-repo/command.d.ts +28 -0
- package/dist/local-repo/command.d.ts.map +1 -0
- package/dist/local-repo/command.js +178 -0
- package/dist/local-repo/command.js.map +1 -0
- package/dist/local-repo/index.d.ts +119 -0
- package/dist/local-repo/index.d.ts.map +1 -0
- package/dist/local-repo/index.js +141 -0
- package/dist/local-repo/index.js.map +1 -0
- 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/mcp-spawn.d.ts +19 -0
- package/dist/mcp-spawn.d.ts.map +1 -1
- package/dist/mcp-spawn.js +52 -26
- package/dist/mcp-spawn.js.map +1 -1
- 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.map +1 -1
- package/dist/program.js +34 -0
- package/dist/program.js.map +1 -1
- package/dist/provider-auth.d.ts +51 -1
- package/dist/provider-auth.d.ts.map +1 -1
- package/dist/provider-auth.js +83 -2
- package/dist/provider-auth.js.map +1 -1
- 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 +283 -68
- 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 +22 -2
- package/dist/task-runner.d.ts.map +1 -1
- package/dist/task-runner.js +142 -25
- package/dist/task-runner.js.map +1 -1
- package/dist/watch.d.ts +21 -0
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +109 -1
- package/dist/watch.js.map +1 -1
- 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"}
|