@henols/vice-mcp 0.2.2 → 0.2.4
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 +2 -2
- package/THIRD-PARTY-NOTICES.md +422 -1
- package/anno-bank.ts +171 -0
- package/anno-cli.ts +1736 -163
- package/anno-confidence.ts +2 -2
- package/anno-derive.ts +6 -6
- package/anno-details.ts +4 -4
- package/anno-enum-gen.ts +416 -30
- package/anno-export-asm.ts +1211 -126
- package/anno-graphics.ts +338 -0
- package/anno-hazard-report.ts +1367 -0
- package/anno-import.ts +495 -0
- package/anno-index.ts +8 -8
- package/anno-join.ts +480 -0
- package/anno-memmap-render.ts +22 -21
- package/anno-provenance-ledger.ts +472 -0
- package/anno-regbits-gen.ts +13 -13
- package/anno-register.ts +159 -0
- package/anno-store-export.ts +661 -0
- package/anno-store.ts +635 -124
- package/anno-symbols.ts +7 -7
- package/anno-tools.ts +1169 -16
- package/anno-types.ts +313 -40
- package/backend-detect.mts +124 -312
- package/build.ts +3 -1
- package/capture-predicate.ts +597 -0
- package/channel-lock.ts +349 -0
- package/evid-ingest.ts +217 -0
- package/evid-reconcile.ts +316 -0
- package/host-tool-client.ts +430 -0
- package/incident-record.ts +23 -12
- package/install-resources.ts +29 -13
- package/memmap-lookup.ts +285 -0
- package/package.json +27 -8
- package/prg-image.ts +1 -2
- package/repo-root.ts +87 -3
- package/resources/backend-detect.mjs +98 -236
- package/resources/broker-control.mjs +220 -54
- package/resources/broker-epoch.mjs +7 -8
- package/resources/broker-kill.mjs +36 -31
- package/resources/broker-launch.mjs +511 -374
- package/resources/broker-state.mjs +69 -24
- package/resources/container-guard.mjs +1 -1
- package/resources/ghidra-project.mjs +790 -0
- package/resources/host-tool.mjs +2533 -0
- package/resources/vice-broker.mjs +434 -290
- package/resources/vice-launcher.sh +127 -9
- package/stock-address.ts +1 -1
- package/stock-condition.ts +1 -1
- package/stock-connect.ts +9 -5
- package/stock-derived.ts +29 -37
- package/stock-diagnose.ts +200 -36
- package/stock-dispatch.ts +179 -77
- package/stock-handler.ts +1 -1
- package/stock-paths.ts +18 -14
- package/stock-petscii.ts +1 -1
- package/stock-protocol.ts +1 -1
- package/stock-recycle.ts +83 -2
- package/stock-reproducible-run.ts +811 -0
- package/stock-run-until.ts +100 -1
- package/stock-symbols.ts +4 -4
- package/stock-timing.ts +1 -1
- package/stop-oracle.ts +167 -0
- package/text-capability-probe.ts +660 -0
- package/text-connect.ts +157 -0
- package/text-protocol.ts +810 -0
- package/text-tools.ts +778 -0
- package/textmon-backtrace.ts +385 -0
- package/textmon-cpuhistory.ts +335 -0
- package/textmon-memmap.ts +494 -0
- package/textmon-profile.ts +458 -0
- package/textmon-registers.ts +748 -0
- package/tools-manifest.stock.json +864 -3
- package/vice-broker-client.ts +253 -108
- package/vice-errors.ts +268 -0
- package/vice-proxy.ts +339 -2144
- package/vsf-slice.ts +640 -0
- package/anno-d64.ts +0 -310
- package/capability-registry.ts +0 -390
- package/refresh-manifest.ts +0 -124
- package/tools-manifest.json +0 -1223
- package/vice-probe.ts +0 -278
- package/vice-sync.ts +0 -336
- package/vice.ts +0 -772
|
@@ -2,126 +2,53 @@
|
|
|
2
2
|
// Compiled by `tsc` from backend-detect.mts. Edit the TypeScript source and rebuild;
|
|
3
3
|
// changes made directly to this file are silently overwritten by the next build, and are never
|
|
4
4
|
// deployed to the host on their own -- install-resources.mjs copies THIS file's on-disk contents
|
|
5
|
-
// verbatim to tools/, so an edit made only here reaches the host but is lost on the very next
|
|
5
|
+
// verbatim to .c64-re-tools/bin/, so an edit made only here reaches the host but is lost on the very next
|
|
6
6
|
// rebuild.
|
|
7
7
|
// backend-detect.mts
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// happen the moment one copy is updated and the other is not (mcpHost()'s
|
|
17
|
-
// own "three inlined copies" incident is the exact regression this file
|
|
18
|
-
// exists to keep from recurring here).
|
|
9
|
+
// FORKRM-01 (plan 52-06): this file used to be the ONE place that decided
|
|
10
|
+
// which VICE build a binary is (the fork's `-mcpserver` flag versus stock's
|
|
11
|
+
// `-binarymonitor`-only surface) and the ONE reader of an environment
|
|
12
|
+
// override naming a backend, anywhere in this tree. There is only one
|
|
13
|
+
// backend now, so there is nothing left to detect between: the `--help`
|
|
14
|
+
// probe (its own spawn, its fork/stock/unknown classifier) and the
|
|
15
|
+
// environment override are both deleted outright, not merely unreachable.
|
|
19
16
|
//
|
|
20
|
-
//
|
|
21
|
-
// -
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
17
|
+
// What SURVIVES, and why:
|
|
18
|
+
// - Binary-path resolution (`resolveBinPath`/`binPathFields`, WR-05) --
|
|
19
|
+
// `vice_ping`'s `resolvedBinaryPath` field and host-tool.mts's sibling
|
|
20
|
+
// binary resolution (`c1541`/`petcat`) both still need "which file did
|
|
21
|
+
// you actually mean", independent of any backend concept.
|
|
22
|
+
// - The on-disk identity/capability cache (BACK-04) -- it still records
|
|
23
|
+
// something worth caching once the backend verdict is gone: the version
|
|
24
|
+
// quad and CPU-history capability a live connect handshake (plan 02-08,
|
|
25
|
+
// stock-connect.ts) attaches to a resolved binary's identity
|
|
26
|
+
// (`resolvedPath`/`mtimeMs`/`sizeBytes`). `readCapabilityRecord()`/
|
|
27
|
+
// `writeCapabilityRecord()` are unchanged in shape; only the cache
|
|
28
|
+
// record's `backend` field is gone, since it never meant anything once
|
|
29
|
+
// there was nothing to distinguish.
|
|
30
|
+
//
|
|
31
|
+
// `ViceBackend` is NARROWED to a single literal (`"stock"`), not deleted:
|
|
32
|
+
// text-capability-probe.ts and stock-connect.ts (both out of this plan's
|
|
33
|
+
// scope -- plan 52-07 owns text-capability-probe.ts's own registry-shaped
|
|
34
|
+
// fork references) still import it as a type-only import, and deleting it
|
|
35
|
+
// outright would break their compile for no benefit this plan is scoped to
|
|
36
|
+
// deliver.
|
|
40
37
|
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// resolve it, and no fixture string anywhere in this tree should ever be
|
|
54
|
-
// presented as real captured output from either build. See the follow-up
|
|
55
|
-
// todo tracked under .planning/todos/pending/ for what a real-hardware run
|
|
56
|
-
// must still confirm.
|
|
57
|
-
import { spawnSync } from "node:child_process";
|
|
38
|
+
// WHAT NOT TO DO:
|
|
39
|
+
// - Do not reintroduce an environment-variable backend override, a
|
|
40
|
+
// --help probe, or any other backend-discrimination mechanism. There is
|
|
41
|
+
// one binary shape now; a second detection mechanism would be solving a
|
|
42
|
+
// problem that no longer exists.
|
|
43
|
+
// - Do not call resolvedBackend() from inside broker-launch.mts's
|
|
44
|
+
// `inFlight` single-owner launch guard. This still performs filesystem
|
|
45
|
+
// I/O (a stat call and, on a fresh or changed identity, a cache write);
|
|
46
|
+
// anything that can block inside that synchronous check-and-set window
|
|
47
|
+
// is the exact failure class the 2026-08-01 triple-launch outage came
|
|
48
|
+
// from (D-03/T-02-25) -- unchanged reasoning from before this plan, only
|
|
49
|
+
// the mechanism inside resolvedBackend() changed.
|
|
58
50
|
import { existsSync, readFileSync, writeFileSync, chmodSync, renameSync, mkdirSync, statSync, } from "node:fs";
|
|
59
51
|
import { join, resolve as resolvePath } from "node:path";
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// classifyHelpOutput() -- pure string classification, no I/O at all.
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
/** Matches on the literal flag tokens D-02 names as the discriminator: the
|
|
64
|
-
* fork's `-mcpserver` flag versus stock's `-binarymonitor`-only surface.
|
|
65
|
-
* `"fork"` wins when BOTH tokens appear (the fork's own VICE tree is a 3.10
|
|
66
|
-
* checkout and accepts both flags) -- checked FIRST, deliberately, so a
|
|
67
|
-
* build that advertises both is classified by the flag that actually makes
|
|
68
|
-
* it the fork, not merely "also has stock's flag too". `"unknown"` when
|
|
69
|
-
* NEITHER token appears -- a real --help transcript that does not match
|
|
70
|
-
* either shape, a probe that spawned nothing at all (empty text), or
|
|
71
|
-
* anything else this function was never taught to recognise. Never throws;
|
|
72
|
-
* pure function of the text it is given. */
|
|
73
|
-
export function classifyHelpOutput(text) {
|
|
74
|
-
const hasFork = text.includes("-mcpserver");
|
|
75
|
-
const hasStock = text.includes("-binarymonitor");
|
|
76
|
-
if (hasFork)
|
|
77
|
-
return "fork";
|
|
78
|
-
if (hasStock)
|
|
79
|
-
return "stock";
|
|
80
|
-
return "unknown";
|
|
81
|
-
}
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// probeBackend() -- the --help probe. spawnSync only, argv array, shell:
|
|
84
|
-
// false, never a shell string and never string interpolation of binPath
|
|
85
|
-
// into a command line (T-02-03's mitigation). Bounded by a 5000ms timeout
|
|
86
|
-
// with kill-on-timeout so a hostile or hung binary cannot stall broker
|
|
87
|
-
// startup (T-02-25's second mitigation half).
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
const PROBE_TIMEOUT_MS = 5000;
|
|
90
|
-
/** `--help` first, falling back to `-help` then `-?` ONLY when a run exits
|
|
91
|
-
* non-zero with EMPTY combined output -- a run that exits non-zero but
|
|
92
|
-
* still printed something (some builds write usage to stderr and exit 1) is
|
|
93
|
-
* already usable and is not retried further. */
|
|
94
|
-
const HELP_FLAG_CANDIDATES = ["--help", "-help", "-?"];
|
|
95
|
-
function defaultSpawnHelp(binPath, flag) {
|
|
96
|
-
try {
|
|
97
|
-
const result = spawnSync(binPath, [flag], {
|
|
98
|
-
encoding: "utf8",
|
|
99
|
-
timeout: PROBE_TIMEOUT_MS,
|
|
100
|
-
killSignal: "SIGKILL",
|
|
101
|
-
});
|
|
102
|
-
const text = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
103
|
-
return { text, exitedZero: result.status === 0 };
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return { text: "", exitedZero: false };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/** Runs the fallback ladder above against `binPath` and classifies whatever
|
|
110
|
-
* text the LAST attempted flag produced. Never throws -- every failure mode
|
|
111
|
-
* (spawn error, timeout, empty output, an exit code the caller does not
|
|
112
|
-
* recognise) flows through to classifyHelpOutput() as ordinary text, which
|
|
113
|
-
* itself never throws either. */
|
|
114
|
-
export function probeBackend(binPath, deps = {}) {
|
|
115
|
-
const spawnHelp = deps.spawnHelp ?? defaultSpawnHelp;
|
|
116
|
-
let text = "";
|
|
117
|
-
for (const flag of HELP_FLAG_CANDIDATES) {
|
|
118
|
-
const outcome = spawnHelp(binPath, flag);
|
|
119
|
-
text = outcome.text;
|
|
120
|
-
if (outcome.exitedZero || text.trim() !== "")
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
return classifyHelpOutput(text);
|
|
124
|
-
}
|
|
125
52
|
/** The client-side capability-decision schema version stamped into every
|
|
126
53
|
* capability record this module writes (see BackendCacheRecord's own field
|
|
127
54
|
* comment). BUMP THIS whenever the client's capability probing or the
|
|
@@ -165,8 +92,7 @@ function readCacheRecord(supervisorDir) {
|
|
|
165
92
|
return null;
|
|
166
93
|
if (typeof parsed.resolvedPath !== "string" ||
|
|
167
94
|
typeof parsed.mtimeMs !== "number" ||
|
|
168
|
-
typeof parsed.sizeBytes !== "number"
|
|
169
|
-
(parsed.backend !== "fork" && parsed.backend !== "stock")) {
|
|
95
|
+
typeof parsed.sizeBytes !== "number") {
|
|
170
96
|
return null;
|
|
171
97
|
}
|
|
172
98
|
const record = {
|
|
@@ -174,7 +100,6 @@ function readCacheRecord(supervisorDir) {
|
|
|
174
100
|
resolvedPath: parsed.resolvedPath,
|
|
175
101
|
mtimeMs: parsed.mtimeMs,
|
|
176
102
|
sizeBytes: parsed.sizeBytes,
|
|
177
|
-
backend: parsed.backend,
|
|
178
103
|
probedAt: typeof parsed.probedAt === "string" ? parsed.probedAt : "",
|
|
179
104
|
};
|
|
180
105
|
if (typeof parsed.versionQuad === "string")
|
|
@@ -186,10 +111,10 @@ function readCacheRecord(supervisorDir) {
|
|
|
186
111
|
return record;
|
|
187
112
|
}
|
|
188
113
|
/** Tmp-sibling -> chmod 0600 -> content -> rename, the SAME atomic-write
|
|
189
|
-
* discipline
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
114
|
+
* discipline vice-broker.mts's writeBrokerRecordFile() already uses -- a
|
|
115
|
+
* crash mid-write can only ever leave a stray tmp sibling behind, never a
|
|
116
|
+
* truncated or empty file at the real cache path that a later read would
|
|
117
|
+
* wrongly accept. */
|
|
193
118
|
function writeCacheRecordAtomic(supervisorDir, record) {
|
|
194
119
|
mkdirSync(supervisorDir, { recursive: true });
|
|
195
120
|
const finalPath = cachePathFor(supervisorDir);
|
|
@@ -223,133 +148,73 @@ function defaultStat(resolvedPath) {
|
|
|
223
148
|
return null;
|
|
224
149
|
}
|
|
225
150
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
//
|
|
230
|
-
// (VICE_BACKEND set) is always answered fresh, on every call, straight from
|
|
231
|
-
// the environment, and never touches this memo (an explicit override can
|
|
232
|
-
// legitimately differ from call to call within, e.g., a test process driving
|
|
233
|
-
// many scenarios; the detected-backend answer for a fixed binary cannot).
|
|
234
|
-
// This is what makes resolvedBackend() answer "once per long-running
|
|
235
|
-
// process" for the case that actually spawns something, while never
|
|
236
|
-
// requiring a caller to somehow signal "this is a fresh scenario" the way
|
|
237
|
-
// container-guard.mts's isInsideContainer() asks callers to pass explicit
|
|
238
|
-
// deps to bypass ITS OWN memo -- here, the override/no-override distinction
|
|
239
|
-
// already IS that signal.
|
|
151
|
+
// Memoised answer -- a long-running process (the real broker) resolves once
|
|
152
|
+
// per process lifetime; a test suite driving many scenarios calls
|
|
153
|
+
// resetResolvedBackendForTests() between them (see that function's own
|
|
154
|
+
// comment).
|
|
240
155
|
let memoisedResult = null;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return;
|
|
249
|
-
warnedBackendUnset = true;
|
|
250
|
-
log(`vice-broker: detected backend "${result.backend}" for ${viceBin} (source: ${result.source}) -- ` +
|
|
251
|
-
`set VICE_BACKEND=stock or VICE_BACKEND=fork to override this detection explicitly`);
|
|
252
|
-
}
|
|
253
|
-
/** Test-only escape hatch: clears the in-process memo and the D-06
|
|
254
|
-
* one-time-note gate. Never called by any real production code path --
|
|
255
|
-
* vice-broker.mts calls resolvedBackend() exactly once per real process
|
|
256
|
-
* lifetime and has no reason to ever reset it; this exists solely so
|
|
257
|
-
* backend-detect.test.ts can drive many distinct scenarios (cache hit, cache
|
|
258
|
-
* miss, indeterminate, ...) in one shared test process without one
|
|
259
|
-
* scenario's memoised answer contaminating the next -- mirroring
|
|
260
|
-
* broker-launch.test.ts's own discipline of restoring module-level state
|
|
261
|
-
* between test cases, made explicit here rather than left to careful test
|
|
262
|
-
* ordering, since this module's memo (unlike buildViceArgs()'s one-time
|
|
263
|
-
* note) has no natural "always widens the same way" ordering to exploit. */
|
|
156
|
+
/** Test-only escape hatch: clears the in-process memo. Never called by any
|
|
157
|
+
* real production code path -- vice-broker.mts calls resolvedBackend()
|
|
158
|
+
* exactly once per real process lifetime and has no reason to ever reset it;
|
|
159
|
+
* this exists solely so backend-detect.test.ts can drive many distinct
|
|
160
|
+
* scenarios (fresh identity, matching cached identity, changed identity, ...)
|
|
161
|
+
* in one shared test process without one scenario's memoised answer
|
|
162
|
+
* contaminating the next. */
|
|
264
163
|
export function resetResolvedBackendForTests() {
|
|
265
164
|
memoisedResult = null;
|
|
266
|
-
warnedBackendUnset = false;
|
|
267
165
|
}
|
|
268
|
-
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* `{ resolvedPath, mtimeMs, sizeBytes }` all match the stored record); on a
|
|
273
|
-
* miss, probes via probeBackend() and writes the cache. Memoises the
|
|
274
|
-
* probe/cache answer in a module-level variable so a long-running process
|
|
275
|
-
* resolves once (see the memo's own comment above for what "once" means
|
|
276
|
-
* here). Never throws: a probe that classifies "unknown" (including a
|
|
277
|
-
* spawn failure or a timeout, both of which probeBackend() already reduces
|
|
278
|
-
* to "unknown") returns a defined `{ backend: "fork", source:
|
|
279
|
-
* "indeterminate", ... }` outcome instead -- "fork" because that is the
|
|
280
|
-
* pre-Phase-2 behaviour every existing install already has, so an
|
|
281
|
-
* undetectable binary degrades to what already worked rather than to
|
|
282
|
-
* nothing. */
|
|
283
|
-
/** WR-05: the ONE place `binPath`/`binPathResolved` are derived, so the four
|
|
284
|
-
* return paths below cannot disagree about what "the binary" means. A resolved
|
|
285
|
-
* absolute path when there is one; the configured name, flagged as unresolved,
|
|
286
|
-
* when there is not. */
|
|
166
|
+
/** WR-05: the ONE place `binPath`/`binPathResolved` are derived, so a
|
|
167
|
+
* resolved absolute path when there is one, or the configured name flagged
|
|
168
|
+
* as unresolved when there is not, is computed identically everywhere this
|
|
169
|
+
* file returns it. */
|
|
287
170
|
function binPathFields(resolvedPath, viceBin) {
|
|
288
171
|
return resolvedPath !== null ? { binPath: resolvedPath, binPathResolved: true } : { binPath: viceBin, binPathResolved: false };
|
|
289
172
|
}
|
|
173
|
+
/** Resolves the emulator binary's identity and (re)initialises the on-disk
|
|
174
|
+
* identity/capability cache record for it when a `supervisorDir` is given.
|
|
175
|
+
* Memoises the answer in a module-level variable so a long-running process
|
|
176
|
+
* resolves once (see the memo's own comment above for what "once" means
|
|
177
|
+
* here). Never throws.
|
|
178
|
+
*
|
|
179
|
+
* FORKRM-01: there is nothing left to detect -- `backend` is always
|
|
180
|
+
* `"stock"`. What this function still does is identity resolution (WR-05's
|
|
181
|
+
* `binPath`/`binPathResolved`) and cache bookkeeping for BACK-04's capability
|
|
182
|
+
* record: when the resolved binary's identity (`resolvedPath`/`mtimeMs`/
|
|
183
|
+
* `sizeBytes`) does not match whatever is already on file -- no record at
|
|
184
|
+
* all, or a record describing a DIFFERENT binary (replaced in place, or a
|
|
185
|
+
* fresh install) -- a fresh identity record is written, with no stale
|
|
186
|
+
* capability fields carried over from a different binary's answer. A
|
|
187
|
+
* matching identity is left untouched, so a capability answer already
|
|
188
|
+
* recorded for THIS binary survives. */
|
|
290
189
|
export function resolvedBackend(deps = {}) {
|
|
190
|
+
if (memoisedResult !== null)
|
|
191
|
+
return memoisedResult;
|
|
291
192
|
const env = deps.env ?? process.env;
|
|
292
193
|
const viceBin = deps.viceBin ?? env.VICE_BIN ?? "x64sc";
|
|
293
|
-
// A direct read of the real environment on the right of this ternary
|
|
294
|
-
// (rather than the generic `env` local above) is deliberate: this file is
|
|
295
|
-
// grep-gated, tree-wide, as the ONE place that ever names this variable
|
|
296
|
-
// directly against the real environment -- `deps.env` (the test-injection
|
|
297
|
-
// seam) still takes precedence when supplied, exactly like every other
|
|
298
|
-
// field on this options object.
|
|
299
|
-
const override = deps.env ? deps.env.VICE_BACKEND : process.env.VICE_BACKEND;
|
|
300
194
|
const resolveBinPath = deps.resolveBinPath ?? defaultResolveBinPath;
|
|
301
|
-
if (override === "stock" || override === "fork") {
|
|
302
|
-
// WR-05: an explicit backend override still resolves the PATH, so
|
|
303
|
-
// `vice_ping` reports a real file rather than the bare name. This is a
|
|
304
|
-
// filesystem lookup only -- existsSync per PATH entry -- and NEVER a spawn,
|
|
305
|
-
// so the override path keeps its "answered fresh, straight from the
|
|
306
|
-
// environment, spawns nothing" property.
|
|
307
|
-
return { backend: override, source: "override", ...binPathFields(resolveBinPath(viceBin, env), viceBin) };
|
|
308
|
-
}
|
|
309
|
-
if (memoisedResult !== null)
|
|
310
|
-
return memoisedResult;
|
|
311
|
-
const log = deps.log ?? defaultLog;
|
|
312
195
|
const stat = deps.stat ?? defaultStat;
|
|
313
|
-
const probe = deps.probe ?? ((bin) => probeBackend(bin));
|
|
314
196
|
const now = deps.now ?? (() => Date.now());
|
|
315
197
|
const resolvedPath = resolveBinPath(viceBin, env);
|
|
316
198
|
const identity = resolvedPath ? stat(resolvedPath) : null;
|
|
317
199
|
const cacheEligible = resolvedPath !== null && identity !== null && typeof deps.supervisorDir === "string";
|
|
318
200
|
if (cacheEligible) {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
201
|
+
const existing = readCacheRecord(deps.supervisorDir);
|
|
202
|
+
const identityMatches = existing !== null &&
|
|
203
|
+
existing.resolvedPath === resolvedPath &&
|
|
204
|
+
existing.mtimeMs === identity.mtimeMs &&
|
|
205
|
+
existing.sizeBytes === identity.sizeBytes;
|
|
206
|
+
if (!identityMatches) {
|
|
207
|
+
writeCacheRecordAtomic(deps.supervisorDir, {
|
|
208
|
+
version: 1,
|
|
209
|
+
resolvedPath: resolvedPath,
|
|
210
|
+
mtimeMs: identity.mtimeMs,
|
|
211
|
+
sizeBytes: identity.sizeBytes,
|
|
212
|
+
probedAt: new Date(now()).toISOString(),
|
|
213
|
+
});
|
|
328
214
|
}
|
|
329
215
|
}
|
|
330
|
-
const
|
|
331
|
-
if (verdict === "unknown") {
|
|
332
|
-
const note = `vice-broker: could not determine whether ${viceBin} is the stock or fork VICE build -- ` +
|
|
333
|
-
`its --help output matched neither the -mcpserver nor the -binarymonitor discriminator. ` +
|
|
334
|
-
`Set VICE_BACKEND=stock or VICE_BACKEND=fork explicitly.`;
|
|
335
|
-
log(note);
|
|
336
|
-
const result = { backend: "fork", source: "indeterminate", ...binPathFields(resolvedPath, viceBin), note };
|
|
337
|
-
memoisedResult = result;
|
|
338
|
-
return result;
|
|
339
|
-
}
|
|
340
|
-
if (cacheEligible) {
|
|
341
|
-
writeCacheRecordAtomic(deps.supervisorDir, {
|
|
342
|
-
version: 1,
|
|
343
|
-
resolvedPath: resolvedPath,
|
|
344
|
-
mtimeMs: identity.mtimeMs,
|
|
345
|
-
sizeBytes: identity.sizeBytes,
|
|
346
|
-
backend: verdict,
|
|
347
|
-
probedAt: new Date(now()).toISOString(),
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
const result = { backend: verdict, source: "probe", ...binPathFields(resolvedPath, viceBin) };
|
|
216
|
+
const result = { backend: "stock", ...binPathFields(resolvedPath, viceBin) };
|
|
351
217
|
memoisedResult = result;
|
|
352
|
-
emitDetectedNote(result, viceBin, log);
|
|
353
218
|
return result;
|
|
354
219
|
}
|
|
355
220
|
/** Reads whatever capability answers (BACK-04) are on record for `binPath`
|
|
@@ -383,15 +248,13 @@ export function readCapabilityRecord(binPath, deps = {}) {
|
|
|
383
248
|
result.capabilitySchema = existing.capabilitySchema;
|
|
384
249
|
return result;
|
|
385
250
|
}
|
|
386
|
-
/** Attaches `{ versionQuad, cpuHistoryAvailable }` to the EXISTING
|
|
387
|
-
*
|
|
388
|
-
*
|
|
251
|
+
/** Attaches `{ versionQuad, cpuHistoryAvailable }` to the EXISTING identity
|
|
252
|
+
* record already on file for `binPath`'s resolved identity -- a no-op, never
|
|
253
|
+
* a throw, when there is no such matching record yet (no supervisorDir
|
|
389
254
|
* given, the binary cannot be resolved or stat'd, or the cache names a
|
|
390
|
-
* different binary or has no
|
|
391
|
-
*
|
|
392
|
-
* already wrote
|
|
393
|
-
* quad and this function must not silently fabricate the field it did not
|
|
394
|
-
* observe either. */
|
|
255
|
+
* different binary or has no record at all). This function never invents an
|
|
256
|
+
* identity record of its own: it can only EXTEND a record resolvedBackend()
|
|
257
|
+
* already wrote. */
|
|
395
258
|
export function writeCapabilityRecord(binPath, capability, deps = {}) {
|
|
396
259
|
if (typeof deps.supervisorDir !== "string")
|
|
397
260
|
return;
|
|
@@ -412,7 +275,6 @@ export function writeCapabilityRecord(binPath, capability, deps = {}) {
|
|
|
412
275
|
resolvedPath,
|
|
413
276
|
mtimeMs: identity.mtimeMs,
|
|
414
277
|
sizeBytes: identity.sizeBytes,
|
|
415
|
-
backend: existing.backend,
|
|
416
278
|
probedAt: existing.probedAt,
|
|
417
279
|
versionQuad: capability.versionQuad,
|
|
418
280
|
cpuHistoryAvailable: capability.cpuHistoryAvailable,
|