@aristanetworks/eos-mcp-server 0.8.0-beta → 0.9.0-beta

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 (88) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +19 -42
  3. package/dist/cli.d.ts +1 -4
  4. package/dist/cli.js +4 -63
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/probe.js +2 -1
  7. package/dist/commands/probe.js.map +1 -1
  8. package/dist/config/diagnostics.d.ts +15 -5
  9. package/dist/config/diagnostics.js +8 -3
  10. package/dist/config/diagnostics.js.map +1 -1
  11. package/dist/config/loadConfig.js +2 -5
  12. package/dist/config/loadConfig.js.map +1 -1
  13. package/dist/config/schema.d.ts +123 -15
  14. package/dist/config/schema.js +14 -1
  15. package/dist/config/schema.js.map +1 -1
  16. package/dist/connection/eosDeviceReader.d.ts +6 -1
  17. package/dist/connection/eosDeviceReader.js +14 -3
  18. package/dist/connection/eosDeviceReader.js.map +1 -1
  19. package/dist/diagnostics/eapiRunner.js +7 -1
  20. package/dist/diagnostics/eapiRunner.js.map +1 -1
  21. package/dist/diagnostics/executor.d.ts +7 -4
  22. package/dist/diagnostics/executor.js +16 -14
  23. package/dist/diagnostics/executor.js.map +1 -1
  24. package/dist/diagnostics/types.d.ts +22 -6
  25. package/dist/eapi/client.js +5 -4
  26. package/dist/eapi/client.js.map +1 -1
  27. package/dist/eapi/types.d.ts +3 -0
  28. package/dist/eapi/types.js.map +1 -1
  29. package/dist/index.js +0 -10
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/createServer.js +21 -21
  32. package/dist/mcp/createServer.js.map +1 -1
  33. package/dist/mcp/tools/ping.d.ts +2 -9
  34. package/dist/mcp/tools/ping.js +13 -26
  35. package/dist/mcp/tools/ping.js.map +1 -1
  36. package/dist/operations/eapiSlots.d.ts +68 -0
  37. package/dist/operations/eapiSlots.js +237 -0
  38. package/dist/operations/eapiSlots.js.map +1 -0
  39. package/dist/operations/readExecution.js +1 -1
  40. package/dist/operations/readExecution.js.map +1 -1
  41. package/dist/serverInfo/buildServerInfo.js +4 -1
  42. package/dist/serverInfo/buildServerInfo.js.map +1 -1
  43. package/dist/serverInfo/types.d.ts +3 -0
  44. package/docs/CHECKPOINT.md +3 -6
  45. package/docs/CODE_WALKTHROUGH.md +6 -11
  46. package/docs/DEMO_SKILLS_YOUTUBE.md +346 -0
  47. package/docs/DESIGN.md +4 -4
  48. package/docs/DIAGNOSTICS.md +26 -74
  49. package/docs/IMPLEMENTATION_PLAN.md +2 -2
  50. package/docs/adr/0002-fail-closed-diagnostic-reservations.md +2 -0
  51. package/docs/adr/0003-durable-diagnostic-ledger.md +2 -0
  52. package/docs/adr/0004-offline-operator-attested-release.md +2 -0
  53. package/docs/adr/0005-multi-destination-ping.md +5 -1
  54. package/docs/adr/0006-in-memory-timed-diagnostic-capacity.md +23 -0
  55. package/docs/adr/0007-per-device-diagnostic-concurrency.md +16 -0
  56. package/docs/adr/0008-read-only-tool-annotations.md +16 -0
  57. package/docs/adr/0009-shared-per-device-eapi-slots.md +40 -0
  58. package/docs/research/eos-diagnostic-lifecycle.md +2 -0
  59. package/docs/research/eos-linter-integration.md +70 -0
  60. package/package.json +1 -1
  61. package/dist/commands/inspectDiagnostics.d.ts +0 -3
  62. package/dist/commands/inspectDiagnostics.js +0 -35
  63. package/dist/commands/inspectDiagnostics.js.map +0 -1
  64. package/dist/commands/releaseDiagnostics.d.ts +0 -9
  65. package/dist/commands/releaseDiagnostics.js +0 -87
  66. package/dist/commands/releaseDiagnostics.js.map +0 -1
  67. package/dist/diagnostics/concurrency.d.ts +0 -33
  68. package/dist/diagnostics/concurrency.js +0 -157
  69. package/dist/diagnostics/concurrency.js.map +0 -1
  70. package/dist/diagnostics/journal.d.ts +0 -14
  71. package/dist/diagnostics/journal.js +0 -317
  72. package/dist/diagnostics/journal.js.map +0 -1
  73. package/dist/diagnostics/journalTypes.d.ts +0 -8
  74. package/dist/diagnostics/journalTypes.js +0 -2
  75. package/dist/diagnostics/journalTypes.js.map +0 -1
  76. package/dist/diagnostics/releaseEvidence.d.ts +0 -502
  77. package/dist/diagnostics/releaseEvidence.js +0 -115
  78. package/dist/diagnostics/releaseEvidence.js.map +0 -1
  79. package/dist/diagnostics/reservationSchema.d.ts +0 -49
  80. package/dist/diagnostics/reservationSchema.js +0 -18
  81. package/dist/diagnostics/reservationSchema.js.map +0 -1
  82. package/dist/diagnostics/statePath.d.ts +0 -4
  83. package/dist/diagnostics/statePath.js +0 -25
  84. package/dist/diagnostics/statePath.js.map +0 -1
  85. package/dist/utils/privateFile.d.ts +0 -3
  86. package/dist/utils/privateFile.js +0 -7
  87. package/dist/utils/privateFile.js.map +0 -1
  88. package/docs/examples/diagnostic-release-evidence.template.json +0 -26
@@ -12,8 +12,8 @@ The server is a permanent read-only MCP service. Scope includes inventory inspec
12
12
  - Inventory-authoritative host/group target resolution.
13
13
  - HTTPS eAPI client with TLS validation, custom CA support, credential validation, request cancellation, and response-size limits.
14
14
  - Eight MCP tools: server info, inventory, probe, ping, show, logging, facts, and running configuration.
15
- - Typed diagnostic execution with inherited opt-in, configurable budgets/timeouts, global admission, no retries, indefinite uncertain-execution reservations, and live sanitized capacity visibility. Write-ahead durable reservations, crash-safe ownership and offline inspection are implemented. Offline operator-attested release, atomic audit and schema migration are implemented. Machine-verified remote completion/deadline qualification remains follow-up work.
16
- - Local `validate-inventory`, `print-server-info`, `probe`, read-only `inspect-diagnostics`, and offline attested `release-diagnostics` commands.
15
+ - Typed diagnostic execution with inherited opt-in, configurable budgets/timeouts, in-memory per-server admission, no retries, uncertain-execution reservations held for the command budget, and live sanitized capacity visibility. Machine-verified remote completion/deadline qualification remains follow-up work.
16
+ - Local `validate-inventory`, `print-server-info`, and `probe` commands.
17
17
  - Bounded concurrency, target counts, command counts, logging messages, device responses, and aggregate tool responses.
18
18
  - Unit, MCP stdio smoke, package-policy, and optional cEOS integration coverage.
19
19
 
@@ -1,5 +1,7 @@
1
1
  # Retain uncertain diagnostic capacity instead of expiring it
2
2
 
3
+ **Status: superseded by [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md).** Uncertain capacity now frees itself after the execution budget.
4
+
3
5
  Persistence follow-up: [ADR 0003](0003-durable-diagnostic-ledger.md) extends this first slice with a durable ledger; the safety-over-availability decision below remains in force.
4
6
 
5
7
  A finite probe count and reply timeout do not establish when an already-delivered eAPI request will start. Releasing a reservation after a guessed command/HTTP window can therefore admit replacement work while the original command can still execute. Prefer safety over availability: retain unknown-completion reservations without automatic expiry, releasing them only after confirmed completion or definitive rejection before execution. When all capacity is uncertain, reject new and queued diagnostics with `diagnostic_capacity_uncertain`; expose sanitized live reservation metadata through server info rather than suggesting automatic retries or a larger cooldown.
@@ -1,5 +1,7 @@
1
1
  # Preserve diagnostic reservations across restart in a private SQLite ledger
2
2
 
3
+ **Status: superseded by [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md).** The ledger and its ownership lock were removed.
4
+
3
5
  Operator recovery follow-up: [ADR 0004](0004-offline-operator-attested-release.md) adds offline attested release and schema-2 audit; the persistence guarantees below remain in force.
4
6
 
5
7
  In-memory fail-closed reservations are insufficient when an MCP process restarts while EOS work may still run or start later. Production diagnostic execution therefore uses Node's built-in SQLite on private persistent local storage: commit a reservation before dispatch, commit confirmed removal before capacity reuse, and restore unfinished records as uncertain. A separate SQLite ownership database holds an exclusive OS-managed lock for the server lifetime, so process crashes release ownership without guessing from PID or lock age. Prefer one active server per ledger over independent processes silently allocating fresh capacity. Existing state is validated and bound to the canonical inventory identity; corruption, missing companion files, incompatible schemas, and write failures block work rather than recreate empty capacity.
@@ -1,5 +1,7 @@
1
1
  # Allow offline operator-attested release with atomic audit
2
2
 
3
+ **Status: superseded by [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md).** `release-diagnostics` and `inspect-diagnostics` were removed.
4
+
3
5
  EOS completion remains unprovable from silence or an interruption error, but permanently retaining uncertain capacity leaves no operational recovery path. The user explicitly authorized offline operator authority grounded in independently verified fencing of running, accepted queued and replayable work. Permit only exact reservation IDs, matching immutable execution identities, a private bounded attestation file and explicit confirmation, under exclusive ledger ownership. This is not automatic proof of EOS completion, a fencing action, a wildcard clear, or an MCP tool.
4
6
 
5
7
  Commit original reservation snapshots, the attestation, content checksums, recording time and actual OS account in the same FULL-synchronous SQLite transaction as every selected deletion. Keep the claimed verifier distinct from OS identity; neither a field label nor an explicit flag proves human presence. The trusted filesystem owner/OS access policy is the authority, and hashes detect accidental inconsistency rather than malicious owner forgery. Exact committed retries return the prior event without further mutation; changed reuse is rejected. Upgrade schema 1 transactionally to schema 2 so older binaries fail closed rather than ignore operator audit. Read-only inspection does not migrate state. False human evidence can violate remote concurrency safety; the program cannot certify external fencing truth.
@@ -1,9 +1,13 @@
1
1
  # Accept multiple destinations in one ping request
2
2
 
3
+ Tool-annotation reasoning superseded by [ADR 0008](0008-read-only-tool-annotations.md): every tool, including ping, now advertises `readOnlyHint: true`.
4
+
5
+ Admission limits refined by [ADR 0007](0007-per-device-diagnostic-concurrency.md): executions now share a per-device limit under a server-wide ceiling instead of one process-wide `deviceConcurrency`.
6
+
3
7
  Testing several destinations from the same target, such as every BGP peer of a switch, required one `eos_ping` call per destination. MCP clients do not reliably run those calls concurrently: measured on 2026-10-28, Claude Code dispatched six pings issued in one turn strictly one at a time, and Codex runs MCP calls serially unless the server connection opts into parallel calls. Labelling ping read-only to change client scheduling would misstate an active diagnostic that generates traffic and writes durable reservations. Instead, `eos_ping` takes `destinations` (one or more unique IP literals) and runs one execution per resolved host and destination concurrently inside the server, so every client gets the same behavior from one call.
4
8
 
5
9
  Make this a breaking change rather than adding a second tool or an optional alias: one interface serves a single ping (a one-element array) and a sweep, and responses always contain one result per host and destination, each naming its destination. The removed `destination` field is rejected with guidance instead of being silently accepted. `summary` counts host/destination executions; `resolved_devices` still lists hosts.
6
10
 
7
- Keep the safety model of ADRs 0001 to 0004 unchanged. Every execution is admitted, journaled, released, or retained as uncertain exactly like a single ping, under the existing shared `deviceConcurrency`. The whole request is validated before any dispatch, the aggregate probe budget counts hosts × destinations × count, and `maxDestinationsPerRequest` bounds request size. The cost is that one cancelled or timed-out request can now leave up to `deviceConcurrency` uncertain reservations from a single host target, as group targets already could. Large requests must fit the overall deadline in successive waves of available capacity. No up-front feasibility rejection or per-request concurrency cap is added: a conservative bound would reject deliberately short deadlines that existing integration tests and operators use, and a per-request cap would diverge from existing group fan-out.
11
+ Keep the safety model of ADRs 0001 to 0004 unchanged ([ADR 0006](0006-in-memory-timed-diagnostic-capacity.md) later replaced the reservation model of 0002 to 0004). Every execution is admitted, journaled, released, or retained as uncertain exactly like a single ping, under the existing shared `deviceConcurrency`. The whole request is validated before any dispatch, the aggregate probe budget counts hosts × destinations × count, and `maxDestinationsPerRequest` bounds request size. The cost is that one cancelled or timed-out request can now leave up to `deviceConcurrency` uncertain reservations from a single host target, as group targets already could. Large requests must fit the overall deadline in successive waves of available capacity. No up-front feasibility rejection or per-request concurrency cap is added: a conservative bound would reject deliberately short deadlines that existing integration tests and operators use, and a per-request cap would diverge from existing group fan-out.
8
12
 
9
13
  Fan-out across destinations belongs to ping's `DiagnosticDefinition`, which prepares one execution per destination. The executor only runs prepared executions on resolved hosts, so a future destination-based diagnostic can reuse the pattern, and diagnostics without destinations such as packet capture are not forced into it.
@@ -0,0 +1,23 @@
1
+ # Replace the durable diagnostic ledger with in-memory, time-bounded capacity
2
+
3
+ Admission limits refined by [ADR 0007](0007-per-device-diagnostic-concurrency.md): `deviceConcurrency` now applies per device, under a new server-wide `maxConcurrentDiagnostics`.
4
+
5
+ Supersedes [ADR 0002](0002-fail-closed-diagnostic-reservations.md), [ADR 0003](0003-durable-diagnostic-ledger.md) and [ADR 0004](0004-offline-operator-attested-release.md). [ADR 0001](0001-non-configuring-diagnostics.md) and [ADR 0005](0005-multi-destination-ping.md) stay in force.
6
+
7
+ ADRs 0002 to 0004 held an execution's slot forever when its completion was unknown, because a delivered eAPI request might in principle sit in an EOS queue and start later. Holding slots forever then required a durable SQLite ledger so restarts could not reset them, an exclusive ownership lock so a second process could not allocate fresh capacity, and an offline operator attestation and audit workflow to recover stuck slots. The exclusive lock was taken at server start whenever any host allowed diagnostics, so only one MCP server per inventory could run. A second chat session, or the desktop app alongside a Code session, failed to start at all, losing every read-only tool as well as `eos_ping`.
8
+
9
+ That machinery guarded a failure mode the project never observed: the lifecycle investigation reports that no delayed-queue fault was reproduced. Being wrong about it costs little. A ping execution is capped at `maxPingCount` 64-byte probes at one-second pacing, changes no configuration, and cEOS ran at most three eAPI commands concurrently per device regardless of server capacity. Against that, the design cost every user concurrent sessions, could permanently remove capacity after one timeout, and required a human attestation to recover.
10
+
11
+ Capacity is now per server process and in memory. A confirmed completion or a rejection established before dispatch frees its slot at once. An execution whose completion is unknown (transport loss, HTTP timeout, malformed or uncorrelated response, cancellation, or a post-dispatch RPC error) keeps its slot as **uncertain** for `commandTimeoutMs`, the configured worst-case execution budget, measured from the moment it became uncertain. The slot then frees itself; a late confirmed response frees it sooner. Uncertain slots count against `deviceConcurrency` like active ones, so new work queues behind them within the overall deadline instead of failing with a dedicated error.
12
+
13
+ Accepted consequences:
14
+
15
+ - `deviceConcurrency` limits one server process, not an inventory. N concurrent sessions can run up to N × `deviceConcurrency` pings. The per-request limits (`maxPingCount`, `maxProbesPerRequest`, `maxDestinationsPerRequest`) and the device's own eAPI concurrency still bound load.
16
+ - A restart forgets uncertain slots. Under normal EOS scheduling the original command has finished within `commandTimeoutMs` of dispatch anyway.
17
+ - If EOS did queue a request for longer than the hold, replacement work could overlap it. This is the risk ADR 0002 refused; it is now accepted as bounded and low-impact for ping.
18
+
19
+ Removed: the SQLite ledger and its ownership lock, `diagnostics.stateFile`, the `inspect-diagnostics` and `release-diagnostics` commands, fencing attestations and release audit, the `diagnostic_capacity_uncertain` and `diagnostic_state_*` error codes, and the `scope`, `storage_error_code` and `rpc_request_id` fields of the capacity snapshot (`reservation_id` is the wire RPC ID). Existing ledger files under `$XDG_STATE_HOME/eos-mcp-server/` are no longer read and can be deleted. JSON-RPC response correlation, write-ahead classification of dispatch state, and the no-automatic-retry rule are unchanged.
20
+
21
+ Ping requests also send EOS's native `runCmds.requestTimeout`, set to `commandTimeoutMs`, so the device interrupts a command that outlives the same budget the server holds an uncertain slot for. It is on by default and can be disabled with `diagnostics.nativeRequestTimeout: false` for an EOS version that rejects the field. It covers execution only, not time queued before execution, and its timeout error is still treated as unknown completion.
22
+
23
+ Revisit this if a diagnostic with real side effects or long remote lifetimes is added, such as packet capture. That operation should get its own lifecycle decision rather than inheriting ping's.
@@ -0,0 +1,16 @@
1
+ # Limit diagnostic concurrency per device, under a server-wide ceiling
2
+
3
+ Refined by [ADR 0009](0009-shared-per-device-eapi-slots.md): reads now share each device's slots, the top-level `deviceConcurrency` (default 3) is the per-device total, and `diagnostics.deviceConcurrency` (default 2) is the diagnostic share.
4
+
5
+ Refines the admission limit of [ADR 0005](0005-multi-destination-ping.md) and [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md). Their reservation lifecycle, uncertain-slot hold, and no-retry rules are unchanged.
6
+
7
+ Until now `diagnostics.deviceConcurrency` (default 5) was one first-come pool for the whole server process; despite its name it did not know which device a slot belonged to. Measured on 2026-10-29 against the cEOS 4.34.3M lab, that made a VTEP full mesh (six hosts, five destinations each, one call per host from six concurrent subagents) take turns: each call took all five slots, but the device ran only three pings at a time. Six direct concurrent eAPI pings to one device finished as three at about 2.1 s and three at about 4.3 s. The two extra slots per call sat queued on the device while idle devices waited for capacity, so calls finished one after another about 2 s apart. The single pool also let one unhealthy device block every other: a few uncertain slots on it held the whole server's capacity for `commandTimeoutMs`.
8
+
9
+ Admission now has two limits. `diagnostics.deviceConcurrency` (default 3, the measured cEOS eAPI concurrency) caps the slots one inventory host may hold. `diagnostics.maxConcurrentDiagnostics` (default 32) caps the slots of the whole server process; `deviceConcurrency` must not exceed it. A slot is granted only while both have room. Waiters are served oldest first, skipping any whose device is still full, so a ping queued for a busy device never holds up a later ping to a free one. Uncertain slots count against their own device and the server-wide ceiling, so an unhealthy device mostly blocks itself. A request queues all its executions with the controller at once rather than through a per-call worker pool, because a host-ordered pool would wait on the first host's queue while later hosts sat idle. `maxProbesPerRequest` still bounds how many executions one request creates.
10
+
11
+ Accepted consequences:
12
+
13
+ - **Breaking:** an existing `deviceConcurrency` setting keeps its value but now applies per device. A configuration that relied on it to cap the whole server must also set `maxConcurrentDiagnostics`.
14
+ - Load on any one device from one server process drops from five to three concurrent pings by default, while total load across devices can rise to 32. N concurrent server processes can still run N times these limits.
15
+ - The capacity snapshot's top-level counts stay server-wide. It adds `device_limit` and a `devices` breakdown (available, active, uncertain and queued slots for each host holding or awaiting a slot); server info adds `limits.diagnostics.max_concurrent_diagnostics`.
16
+ - The default of 3 matches cEOS 4.34.3M only. Physical platforms may run more or fewer eAPI commands concurrently; extra slots beyond a device's own limit only queue on the device while holding a reservation.
@@ -0,0 +1,16 @@
1
+ # Advertise every tool, including ping, as read-only to MCP clients
2
+
3
+ Supersedes the tool-annotation reasoning of [ADR 0005](0005-multi-destination-ping.md). Its multi-destination request shape is unchanged.
4
+
5
+ MCP clients decide from tool annotations whether independent calls may run at the same time. Codex 0.165.0 runs MCP calls concurrently only when the server connection sets `supports_parallel_tool_calls` or the tool advertises `readOnlyHint: true`. Claude Code runs only read-only MCP tools concurrently with each other and has no per-server opt-in; measured on 2026-10-28, six unannotated pings issued in one turn ran strictly one at a time, and overlapping them took one subagent per call. No EOS tool advertised annotations, so show commands were serialized as well as pings.
6
+
7
+ ADR 0005 declined `readOnlyHint` on `eos_ping` because ping generates traffic and wrote durable reservations. [ADR 0006](0006-in-memory-timed-diagnostic-capacity.md) removed the durable ledger, so ping now writes nothing that outlives the server process. The MCP specification defines `readOnlyHint` as "does not modify its environment." Ping changes no configuration, routing, or other persistent device state; its residue is transient neighbor-cache entries and counters, the same kind of effect a show command leaves in eAPI counters and AAA accounting. Generating bounded probe traffic is observation of the network, not modification of it. [ADR 0001](0001-non-configuring-diagnostics.md) still holds that non-configuring diagnostics are not harmless reads, but annotations are advisory hints that the specification tells clients not to trust for safety; the real controls for diagnostics are server-side and apply regardless of how a client schedules calls.
8
+
9
+ Every tool now advertises `readOnlyHint: true`. `eos_ping` also advertises `openWorldHint: true`, because its destinations need not be in inventory; the other tools advertise `openWorldHint: false`, because they only reach inventory devices. `destructiveHint` and `idempotentHint` are omitted, since clients ignore them for read-only tools.
10
+
11
+ Accepted consequences:
12
+
13
+ - Clients may run several pings, or pings alongside show commands, at once. Inventory diagnostic authorization, packet and destination bounds, per-device and server-wide concurrency ([ADR 0007](0007-per-device-diagnostic-concurrency.md)), and the overall timeout still apply. Concurrent calls that exceed a device's limit queue inside their overall deadline and can time out rather than fail fast.
14
+ - Read tools (show, logging, facts, running config, probe) have only their per-call limits: the top-level `deviceConcurrency` bounds fan-out within one call, but nothing bounds load on a device across concurrent read calls. Read-only eAPI commands are cheap compared with ping, so this is accepted for now; a cross-call read limiter would be a separate decision. [ADR 0009](0009-shared-per-device-eapi-slots.md) since makes reads share each device's slots with diagnostics.
15
+ - Some clients may also use `readOnlyHint` to relax confirmation prompts, so a user may be asked less often before a ping runs. Ping still requires `mcp_diagnostics_allowed: true` in inventory.
16
+ - A future diagnostic that does change device state or exposes sensitive data, such as packet capture, must not inherit this annotation without its own decision.
@@ -0,0 +1,40 @@
1
+ # Share one per-device eAPI slot budget between reads and diagnostics
2
+
3
+ **Status: accepted.**
4
+
5
+ Extends [ADR 0007](0007-per-device-diagnostic-concurrency.md) from diagnostics to every eAPI request, and closes the read-tool gap that [ADR 0008](0008-read-only-tool-annotations.md) accepted.
6
+
7
+ Since every tool advertises `readOnlyHint`, clients run independent calls at once. Diagnostics are bounded per device across calls, but reads are not: the top-level `deviceConcurrency` (default 5) only limits how many hosts one read call contacts at a time. Measured on 2026-10-29 against cEOS 4.34.3M (node2-1, direct eAPI, 5-packet pings of about 4.2 s):
8
+
9
+ - 3 pings plus 1 `show version`: the show finished at 4.17 s, after the pings; alone it takes about 0.33 s.
10
+ - 2 pings plus 4 shows: all shows finished by 0.38 s.
11
+ - 5 pings: 3 finished at 4.2 s and 2 at 8.3 s.
12
+
13
+ The device runs at most three eAPI requests at a time, shared by pings and reads. Three concurrent 10-packet pings therefore hold every read to that device for about 10 s, the default `readTimeoutMs`, so ping traffic can make show commands time out. A separate read limiter would bound read load but would not stop pings from filling the device.
14
+
15
+ ## Decision
16
+
17
+ One admission controller per server process (`src/operations/eapiSlots.ts`, generalizing the ADR 0007 controller) grants **eAPI slots** for every device request, read or diagnostic:
18
+
19
+ - Top-level `deviceConcurrency` (default **3**, was 5) becomes what its name says: the slots one inventory host may hold across all calls, matching the measured cEOS eAPI concurrency.
20
+ - `diagnostics.deviceConcurrency` (default **2**, was 3) caps how many of those slots diagnostics may hold on one host, so a slot stays free for reads. It must not exceed the top-level value; setting them equal accepts that pings may fill a device.
21
+ - `diagnostics.maxConcurrentDiagnostics` (default 32) still caps diagnostics across the process. Reads get no server-wide ceiling.
22
+ - The new top-level `readFanOut` (default 10) caps how many hosts one read call contacts at once, taking over the per-call role the old `deviceConcurrency` had. A 50-host read no longer opens 50 connections at once.
23
+ - `EapiDeviceReader` takes a slot before each request and frees it when the request settles or is aborted, so `readTimeoutMs` starts at dispatch and queue time counts only against `overallOperationTimeoutMs`. Reads do not take ADR 0006's uncertain hold: they are idempotent, bounded by `readTimeoutMs`, and cheap compared with a ping.
24
+ - Waiters are served oldest first, skipping any that cannot run yet. Reads get no priority over diagnostics; the reserved slot already stops pings from filling a device.
25
+
26
+ ## Consequences
27
+
28
+ - **Breaking:** top-level `deviceConcurrency` changes meaning from per-call fan-out to per-device slots shared by every call, and its default drops from 5 to 3; per-call fan-out moves to `readFanOut`. `diagnostics.deviceConcurrency` drops from 3 to 2 and must not exceed the top-level value, so a configuration that raised only the diagnostic value must raise both.
29
+ - Pings alone can never fill a device: a read waits only for other reads ahead of it, never for pings. Concurrent pings per device drop from three to two.
30
+ - Many concurrent reads to one device now queue in the server instead of on the device, so a read's `readTimeoutMs` no longer includes time spent behind other requests.
31
+ - The `diagnostics.capacity` snapshot keeps its server-wide diagnostic counts and adds `eapi_device_limit`, plus `read_active_count` and `read_queued_count` for each listed host. Server info adds `limits.read_fan_out`.
32
+ - The `probe` CLI command runs in its own process, so it builds its own slots from the same settings.
33
+ - Other platforms may run more or fewer concurrent eAPI requests; both per-device limits stay configurable.
34
+
35
+ ## Alternatives considered
36
+
37
+ - **Separate read limiter per device.** Bounds read load but leaves pings able to fill the device and time out reads.
38
+ - **Only lower `diagnostics.deviceConcurrency` to 2.** Leaves one device slot for reads with no new code, but reads stay unbounded across calls and can still fill the device themselves.
39
+ - **Give reads the uncertain hold.** Adds the lifecycle cost of ADR 0006 for requests that are idempotent and short.
40
+ - **Queue every host of a read call at once, as diagnostics do.** Simpler, but lets one group read open `maxReadTargets` connections at once.
@@ -1,5 +1,7 @@
1
1
  # EOS HTTPS eAPI diagnostic lifecycle
2
2
 
3
+ > Historical investigation. [ADR 0006](../adr/0006-in-memory-timed-diagnostic-capacity.md) later chose to hold uncertain capacity for the command budget instead of indefinitely, accepting the unreproduced delayed-queue risk described here.
4
+
3
5
  ## Conclusion
4
6
 
5
7
  **No dispatch-relative deadline, durable execution-token lifecycle, or definitive cancellation guarantee was qualified for ping.** However, EOS **does expose a native `runCmds.requestTimeout`**: saying eAPI has only a client-side timeout would be incorrect. Its installed implementation is an execution-stage interruption timer, not a verified admission-to-process-exit bound. Losing HTTP completion evidence must therefore remain an uncertain reservation, not become free capacity after a cooldown.
@@ -0,0 +1,70 @@
1
+ # eos-linter integration assessment
2
+
3
+ Researched 2026-10-21. Recommendation: reuse its **EOS-derived grammar and offline matching approach**, but first obtain an EXEC/show-command schema. Its published configuration schema cannot validate this server's general `show` commands.
4
+
5
+ ## Evidence and provenance
6
+
7
+ The requested [GitHub repository](https://github.com/mitchv85/eos-linter) and its API returned 404 through both the connected GitHub tool and anonymous HTTPS. This does not establish whether it is private, moved, or removed. No repository commit or source tree could be verified.
8
+
9
+ Instead, inspected the author's published **0.6.4** extension, published **2026-10-16T13:38:47Z**, using [Open VSX metadata](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4), its [manifest](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/package.json), [README](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/readme.md), [changelog](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/changelog.md), and [VSIX](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.vsix). The archive's calculated SHA-256 matched the [published checksum](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.sha256): `959ba0c66e3e96e384b2fc00eeef57cc23600e977540e2d49ab60c1ac0e19c89`. No downloaded JavaScript or build/probe scripts were executed.
10
+
11
+ ## What is reusable
12
+
13
+ - **TypeScript core.** The manifest's `build:core` bundles `src/core/*.ts` as Node-targeted ESM; the extension bundles `src/extension.ts` as CommonJS, with `vscode` external. No runtime `dependencies` are declared. Development dependencies include TypeScript, esbuild, VS Code types/package tooling and grammar-test libraries. This is compatible in principle with our TypeScript/Node architecture. [Manifest](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/package.json)
14
+ - **Grammar rather than command-string lists.** The shipped `out/schema/eos-4.36.2F.json` contains original syntax strings (`s`), compiled expression arrays (`x`), indexes by first keyword, mode transitions, typed placeholders, hidden commands and completion corrections. Example expression tags include sequences (`s`), alternatives (`a`), optional expressions (`o`) and placeholders (`p`). This structure can support both candidate completion and validation. [VSIX, bundled schema](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.vsix)
15
+ - **Offline validation and completion.** The README describes mode-aware syntax checks, numeric/address/interface types, abbreviations, and next-token completion mirroring EOS `?`; document-defined names improve suggestions. Runtime bundle inspection found only `require("vscode")`, local schema loading and editor providers; no `runCmds`, `getCommandCompletions`, HTTP client or subprocess module. Ordinary linting does not submit candidate commands to a switch. [README](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/readme.md), [VSIX](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.vsix)
16
+ - **Permissive project license.** The packaged MIT license names Copyright 2026 PacketAnglers and requires preservation of its copyright and permission notice when copying substantial portions. [License](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/LICENSE.txt)
17
+
18
+ ## The critical coverage gap
19
+
20
+ The schema's own `source` metadata states:
21
+
22
+ ```json
23
+ {
24
+ "version": "4.36.2F-49692818.4362F (engineering build)",
25
+ "command": "show cli commands all-modes | json",
26
+ "capturedUtc": "2026-09-28T19:09:06Z",
27
+ "excluded": [
28
+ "EnableMode",
29
+ "UnprivMode",
30
+ "GlobalConfigModeWithHistory",
31
+ "ConfigSessionMode"
32
+ ]
33
+ }
34
+ ```
35
+
36
+ The hidden-command source uses `show cli commands all-modes hidden | json` with the same exclusions. Neither `EnableMode` nor `UnprivMode` exists in the schema's `modes`. The 72 syntax strings starting with `show` are configuration-context forms such as `show pending`, `show active` and `show diff`, not a general operational-command catalog. **Do not use absence from this schema to reject `show bgp ...` or other EXEC commands.** [VSIX, bundled schema](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.vsix)
37
+
38
+ The schema reports 769 modes, 6,690 syntax strings, 6,512 compiled entries and **201 truncated entries**, plus 209 compiled hidden entries. It is **2,275,374 bytes**, with additional measured platform/type/reference rules. The README identifies EOS 4.36.2F as its baseline and lists per-release schema selection as future work. Validation is therefore version/platform dependent, with incomplete listing coverage and document-local semantics; a syntactically accepted command does not establish correct troubleshooting intent or authorization. [VSIX](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.vsix), [README](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/readme.md)
39
+
40
+ ## Integration shape
41
+
42
+ These are proposed interfaces, not existing eos-linter exports:
43
+
44
+ ```ts
45
+ suggestShowCommands({ target, prefix, limit })
46
+ validateShowCommands({ target, commands })
47
+ ```
48
+
49
+ Expose them as separate read-only MCP tools, backed by a `commandCatalog` module. Suggestions should return short next-token/syntax results with schema release, platform and coverage. Validation should distinguish `valid`, `invalid`, and `unknown`, including incomplete syntax, expected tokens and unsupported schema coverage. For groups, return device-specific compatibility and require every target to pass any configured enforcement rule.
50
+
51
+ Keep three separate decisions: **policy allowed**, **grammar valid**, and **appropriate for the investigation**. Our existing [command guard](../../src/eapi/commands.ts) already enforces single-line `show` input and rejects output modifiers/shell metacharacters. Preserve it before catalog matching and execution. [runShow](../../src/show/runShow.ts) provides the service seam before `reader.runShowCommands`; catalog matches must never authorize configuration or other non-show commands. Initial catalog validation should be advisory while coverage is established. Intent-to-command recommendations still need curated troubleshooting guidance.
52
+
53
+ Prefer extracting or obtaining an upstream library with maintained parser/compiler APIs over importing the VS Code extension. The VSIX exports only `activate` and `deactivate`; core functions are bundled and minified, and its schema loader explicitly requires `GlobalConfigMode` and transitions. The manifest has no library exports, CLI linter or service protocol. Source access is needed to verify separable modules, exact signatures and test contracts before implementation. [Manifest](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/package.json), [VSIX](https://open-vsx.org/api/PacketAnglers/eos-linter/0.6.4/file/PacketAnglers.eos-linter-0.6.4.vsix)
54
+
55
+ ## Smallest useful experiment
56
+
57
+ 1. On a lab device, capture the EXEC/`EnableMode` command listing and confirm it includes representative `show version`, BGP, interfaces and routing syntaxes. Test the trusted discovery command `show cli commands all-modes` using eAPI JSON format, since our public guard rejects `| json`. Listing availability, required privilege and eAPI JSON support remain unverified; the connection attempts below did not return command results.
58
+ 2. Extract a bounded show-only catalog; check whether its syntax fits the linter's expression compiler, especially truncated strings, dynamic placeholders and abbreviations. Compare accepted, incomplete and refused examples against that EOS release without automatically executing arbitrary candidates.
59
+ 3. Prototype offline prefix completion and advisory validation. Pin schema provenance; key live caches by device/release/platform and credential role, with a TTL. Never return the full catalog through MCP: the existing config schema alone exceeds our default 1 MB response limit. Preserve target resolution, timeouts, limits and `AppError` handling.
60
+ 4. Treat config auditing as a separate possible read-only feature using `eos_get_running_config` output. That fits the published schema more directly, but it does not solve operational command discovery and requires full-document/version caveats.
61
+
62
+ Do not implement validation by passing a candidate to eAPI `runCmds`: that executes it. The linter's lab oracle/probe scripts built its evidence; they are not the offline validation runtime and were not available for source review. A community [eAPI tutorial](https://github.com/arista-netdevops-community/arista_eos_automation_with_eAPI#the-getcommandcompletions-method) describes `getCommandCompletions`, but labels those methods informational and advises using only `runCmds`; completion RPC should remain a separate lab investigation, not an assumed supported production API.
63
+
64
+ ## Containerlab validation scope
65
+
66
+ The user confirmed that containerized EOS is available for testing and that some commands supported on physical devices may be absent in cEOS. A cEOS-derived catalog must therefore describe its observed platform and release; it cannot be a universal EOS allowlist. Missing catalog entries should produce `unknown` outside that catalog's coverage. A command refused by a cEOS device establishes only that target's observed behavior, not that physical EOS refuses it. Syntax matching also does not establish feature configuration, hardware availability, privileges or JSON output support.
67
+
68
+ Use cEOS for parser/completion integration and supported read-path smoke checks. Keep physical-device command fixtures as separate evidence with release/model provenance, and leave hardware-specific behavior unverified until tested on a suitable device. Distinguish syntax results from operational failures; connectivity or authentication errors provide no evidence about command validity.
69
+
70
+ Local checks on 2026-10-21 found eight running containers using image `ceos-lab:4.34.3M`. The connected MCP inventory's `arista_ceos` group matches their names and management addresses; Docker inspection confirmed `node1-1` at `172.20.20.11` and `node2-1` at `172.20.20.21`. MCP requests for facts and `show cli commands` in JSON format returned `eapi_transport_error` / `EHOSTUNREACH` on these two targets. No command listing or device-reported EOS version was obtained. Connectivity from the MCP server must be established before drawing command-support conclusions. These checks did not change device configuration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aristanetworks/eos-mcp-server",
3
- "version": "0.8.0-beta",
3
+ "version": "0.9.0-beta",
4
4
  "description": "MCP server for Arista EOS eAPI",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,3 +0,0 @@
1
- import type { ResolvedServerConfig } from "../config/schema.js";
2
- /** Offline/read-only: no credentials, device calls, ownership takeover, or capacity reset. */
3
- export declare function runInspectDiagnostics(config: ResolvedServerConfig, asJson: boolean): Promise<number>;
@@ -1,35 +0,0 @@
1
- import path from "node:path";
2
- import { toErrorCode, toErrorMessage } from "../core/errors.js";
3
- import { resolveDiagnosticStateFile } from "../diagnostics/statePath.js";
4
- import { prettyJson } from "../utils/json.js";
5
- /** Offline/read-only: no credentials, device calls, ownership takeover, or capacity reset. */
6
- export async function runInspectDiagnostics(config, asJson) {
7
- try {
8
- const file = resolveDiagnosticStateFile(config);
9
- const { inspectDiagnosticJournal } = await import("../diagnostics/journal.js");
10
- const state = inspectDiagnosticJournal(file);
11
- const output = { state_file: path.basename(file), schema_version: state.schema_version, inventory_identity: state.inventory_identity,
12
- unresolved_count: state.reservations.length, reservations: state.reservations, release_audit: state.release_audit };
13
- if (asJson) {
14
- console.log(prettyJson(output));
15
- }
16
- else {
17
- console.log(`Diagnostic ledger: ${output.state_file}`);
18
- console.log(`Inventory identity: ${output.inventory_identity}`);
19
- console.log(`Unresolved reservations: ${output.unresolved_count}`);
20
- for (const reservation of output.reservations) {
21
- console.log(`- ${reservation.reservation_id}: ${reservation.inventory_hostname} recorded ${reservation.state}${reservation.reason_code === undefined ? "" : ` (${reservation.reason_code})`}; RPC ID: ${reservation.rpc_request_id ?? "(not recorded)"}`);
22
- }
23
- console.log(`Operator release audit records: ${output.release_audit.length} (use --json for evidence and OS identity)`);
24
- console.log("Recorded state is not proof of remote completion. No reservation was cleared.");
25
- }
26
- return 0;
27
- }
28
- catch (error) {
29
- if (!asJson)
30
- throw error;
31
- console.log(prettyJson({ error_code: toErrorCode(error, "diagnostic_state_invalid"), message: toErrorMessage(error) }));
32
- return 1;
33
- }
34
- }
35
- //# sourceMappingURL=inspectDiagnostics.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"inspectDiagnostics.js","sourceRoot":"","sources":["../../src/commands/inspectDiagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAA4B,EAAE,MAAe;IACvF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,0BAA0B,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAC/E,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;YAClI,gBAAgB,EAAE,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;QACtH,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,sBAAsB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC;YAChE,OAAO,CAAC,GAAG,CAAC,4BAA4B,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACnE,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;gBAC9C,OAAO,CAAC,GAAG,CAAC,KAAK,WAAW,CAAC,cAAc,KAAK,WAAW,CAAC,kBAAkB,aAAa,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,WAAW,GAAG,aAAa,WAAW,CAAC,cAAc,IAAI,gBAAgB,EAAE,CAAC,CAAC;YAC5P,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,mCAAmC,MAAM,CAAC,aAAa,CAAC,MAAM,4CAA4C,CAAC,CAAC;YACxH,OAAO,CAAC,GAAG,CAAC,+EAA+E,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,MAAM;YAAE,MAAM,KAAK,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,WAAW,CAAC,KAAK,EAAE,0BAA0B,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACxH,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
@@ -1,9 +0,0 @@
1
- import type { ResolvedServerConfig } from "../config/schema.js";
2
- export interface ReleaseDiagnosticsOptions {
3
- reservationIds?: string[];
4
- evidenceFile?: string;
5
- confirmFenced?: boolean;
6
- json?: boolean;
7
- }
8
- /** Offline operator authority: no credentials, device calls, evidence fetching, or MCP reset. */
9
- export declare function runReleaseDiagnostics(config: ResolvedServerConfig, options: ReleaseDiagnosticsOptions): Promise<number>;
@@ -1,87 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import { constants } from "node:fs";
3
- import path from "node:path";
4
- import { parseDocument } from "yaml";
5
- import { AppError, toErrorCode, toErrorMessage } from "../core/errors.js";
6
- import { diagnosticInventoryIdentity, resolveDiagnosticStateFile } from "../diagnostics/statePath.js";
7
- import { prettyJson } from "../utils/json.js";
8
- import { isPrivateRegularFile } from "../utils/privateFile.js";
9
- const MAX_EVIDENCE_BYTES = 65_536;
10
- async function readEvidence(file) {
11
- let handle;
12
- try {
13
- const before = await fs.lstat(file);
14
- if (!before.isFile() || before.isSymbolicLink()) {
15
- throw new AppError("diagnostic_release_evidence_invalid", "Evidence must be a regular file, not a symlink or device");
16
- }
17
- handle = await fs.open(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
18
- const stat = await handle.stat();
19
- if (!isPrivateRegularFile(stat) || stat.ino !== before.ino || stat.dev !== before.dev) {
20
- throw new AppError("diagnostic_release_evidence_invalid", "Evidence must be a private owner-controlled file (POSIX mode 0600)");
21
- }
22
- const buffer = Buffer.alloc(MAX_EVIDENCE_BYTES + 1);
23
- let length = 0;
24
- while (length < buffer.length) {
25
- const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
26
- if (bytesRead === 0)
27
- break;
28
- length += bytesRead;
29
- }
30
- if (length > MAX_EVIDENCE_BYTES) {
31
- throw new AppError("diagnostic_release_evidence_too_large", "Evidence JSON must not exceed 65536 bytes; release smaller explicit batches");
32
- }
33
- const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length));
34
- const value = JSON.parse(text);
35
- if (parseDocument(text, { uniqueKeys: true, prettyErrors: false }).errors.length > 0) {
36
- throw new AppError("diagnostic_release_evidence_invalid", "Evidence JSON must not contain duplicate or ambiguous object keys");
37
- }
38
- return value;
39
- }
40
- catch (error) {
41
- if (error instanceof AppError)
42
- throw error;
43
- throw new AppError("diagnostic_release_evidence_invalid", "Cannot read a private regular UTF-8 JSON evidence file");
44
- }
45
- finally {
46
- await handle?.close();
47
- }
48
- }
49
- /** Offline operator authority: no credentials, device calls, evidence fetching, or MCP reset. */
50
- export async function runReleaseDiagnostics(config, options) {
51
- try {
52
- if (options.confirmFenced !== true) {
53
- throw new AppError("diagnostic_release_confirmation_required", "Explicit --confirm-fenced is required after independent fencing verification");
54
- }
55
- if (options.evidenceFile === undefined) {
56
- throw new AppError("cli_missing_evidence_file", "release-diagnostics requires --evidence-file");
57
- }
58
- if (options.reservationIds === undefined || options.reservationIds.length === 0) {
59
- throw new AppError("cli_missing_reservation", "release-diagnostics requires explicit --reservation-id values");
60
- }
61
- const evidence = await readEvidence(path.resolve(options.evidenceFile));
62
- const file = resolveDiagnosticStateFile(config);
63
- const expected = config.inventoryPath === undefined ? undefined : diagnosticInventoryIdentity(config.inventoryPath);
64
- const { releaseDiagnosticReservations } = await import("../diagnostics/journal.js");
65
- const result = releaseDiagnosticReservations(file, {
66
- reservationIds: options.reservationIds, evidence, confirmedFenced: true,
67
- ...(expected === undefined ? {} : { expectedInventoryIdentity: expected })
68
- });
69
- if (options.json) {
70
- console.log(prettyJson({ ...result, authority: "operator_attestation" }));
71
- }
72
- else {
73
- console.log(`${result.already_committed ? "Already committed" : "Operator release committed"}: ${result.release_id}`);
74
- for (const id of result.released_reservation_ids)
75
- console.log(`- ${id}`);
76
- console.log("Authority: operator attestation, not machine-confirmed EOS completion. Inspect the durable audit before any further recovery.");
77
- }
78
- return 0;
79
- }
80
- catch (error) {
81
- if (!options.json)
82
- throw error;
83
- console.log(prettyJson({ error_code: toErrorCode(error, "diagnostic_release_failed"), message: toErrorMessage(error) }));
84
- return 1;
85
- }
86
- }
87
- //# sourceMappingURL=releaseDiagnostics.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"releaseDiagnostics.js","sourceRoot":"","sources":["../../src/commands/releaseDiagnostics.ts"],"names":[],"mappings":"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"}
@@ -1,33 +0,0 @@
1
- import type { DiagnosticCapacitySnapshot, DiagnosticRequest } from "./types.js";
2
- import type { DiagnosticJournal } from "./journalTypes.js";
3
- interface DiagnosticReservationIntent {
4
- inventoryHostname: string;
5
- diagnostic: DiagnosticRequest["kind"];
6
- }
7
- export interface DiagnosticReservation {
8
- readonly requestId: string;
9
- complete(): void;
10
- markUncertain(reasonCode: string): void;
11
- }
12
- /** One instance per server; uncertain remote execution never expires into free capacity. */
13
- export declare class DiagnosticConcurrency {
14
- private readonly limit;
15
- private readonly journal?;
16
- private readonly reservations;
17
- private readonly waiting;
18
- private storageError;
19
- private closed;
20
- constructor(limit: number, journal?: DiagnosticJournal | undefined);
21
- close(): void;
22
- snapshot(): DiagnosticCapacitySnapshot;
23
- assertUsableCapacity(): void;
24
- acquire(signal: AbortSignal, intent: DiagnosticReservationIntent): Promise<DiagnosticReservation>;
25
- private reserve;
26
- private complete;
27
- private markUncertain;
28
- private isUncertainExhausted;
29
- private failStorage;
30
- private rejectWaiters;
31
- private capacityError;
32
- }
33
- export {};