@aristanetworks/eos-mcp-server 0.8.0-beta → 0.8.1-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.
- package/CHANGELOG.md +7 -0
- package/README.md +4 -30
- package/dist/cli.d.ts +1 -4
- package/dist/cli.js +4 -63
- package/dist/cli.js.map +1 -1
- package/dist/config/diagnostics.d.ts +10 -5
- package/dist/config/diagnostics.js +3 -2
- package/dist/config/diagnostics.js.map +1 -1
- package/dist/config/loadConfig.js +1 -5
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.d.ts +28 -14
- package/dist/diagnostics/concurrency.d.ts +7 -11
- package/dist/diagnostics/concurrency.js +38 -80
- package/dist/diagnostics/concurrency.js.map +1 -1
- package/dist/diagnostics/eapiRunner.js +7 -1
- package/dist/diagnostics/eapiRunner.js.map +1 -1
- package/dist/diagnostics/executor.d.ts +1 -2
- package/dist/diagnostics/executor.js +2 -3
- package/dist/diagnostics/executor.js.map +1 -1
- package/dist/diagnostics/types.d.ts +3 -4
- package/dist/eapi/client.js +5 -4
- package/dist/eapi/client.js.map +1 -1
- package/dist/eapi/types.d.ts +3 -0
- package/dist/eapi/types.js.map +1 -1
- package/dist/index.js +0 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp/createServer.js +2 -19
- package/dist/mcp/createServer.js.map +1 -1
- package/dist/mcp/tools/ping.d.ts +0 -7
- package/dist/mcp/tools/ping.js +13 -26
- package/dist/mcp/tools/ping.js.map +1 -1
- package/dist/serverInfo/buildServerInfo.js +2 -1
- package/dist/serverInfo/buildServerInfo.js.map +1 -1
- package/dist/serverInfo/types.d.ts +1 -0
- package/docs/CHECKPOINT.md +3 -6
- package/docs/CODE_WALKTHROUGH.md +6 -11
- package/docs/DESIGN.md +3 -3
- package/docs/DIAGNOSTICS.md +18 -69
- package/docs/IMPLEMENTATION_PLAN.md +2 -2
- package/docs/adr/0002-fail-closed-diagnostic-reservations.md +2 -0
- package/docs/adr/0003-durable-diagnostic-ledger.md +2 -0
- package/docs/adr/0004-offline-operator-attested-release.md +2 -0
- package/docs/adr/0005-multi-destination-ping.md +1 -1
- package/docs/adr/0006-in-memory-timed-diagnostic-capacity.md +21 -0
- package/docs/research/eos-diagnostic-lifecycle.md +2 -0
- package/package.json +1 -1
- package/dist/commands/inspectDiagnostics.d.ts +0 -3
- package/dist/commands/inspectDiagnostics.js +0 -35
- package/dist/commands/inspectDiagnostics.js.map +0 -1
- package/dist/commands/releaseDiagnostics.d.ts +0 -9
- package/dist/commands/releaseDiagnostics.js +0 -87
- package/dist/commands/releaseDiagnostics.js.map +0 -1
- package/dist/diagnostics/journal.d.ts +0 -14
- package/dist/diagnostics/journal.js +0 -317
- package/dist/diagnostics/journal.js.map +0 -1
- package/dist/diagnostics/journalTypes.d.ts +0 -8
- package/dist/diagnostics/journalTypes.js +0 -2
- package/dist/diagnostics/journalTypes.js.map +0 -1
- package/dist/diagnostics/releaseEvidence.d.ts +0 -502
- package/dist/diagnostics/releaseEvidence.js +0 -115
- package/dist/diagnostics/releaseEvidence.js.map +0 -1
- package/dist/diagnostics/reservationSchema.d.ts +0 -49
- package/dist/diagnostics/reservationSchema.js +0 -18
- package/dist/diagnostics/reservationSchema.js.map +0 -1
- package/dist/diagnostics/statePath.d.ts +0 -4
- package/dist/diagnostics/statePath.js +0 -25
- package/dist/diagnostics/statePath.js.map +0 -1
- package/dist/utils/privateFile.d.ts +0 -3
- package/dist/utils/privateFile.js +0 -7
- package/dist/utils/privateFile.js.map +0 -1
- package/docs/examples/diagnostic-release-evidence.template.json +0 -26
|
@@ -39,7 +39,8 @@ export function buildServerInfo(context, diagnosticCapacity) {
|
|
|
39
39
|
ping_reply_timeout_ms: context.config.diagnostics.pingReplyTimeoutMs,
|
|
40
40
|
command_timeout_ms: context.config.diagnostics.commandTimeoutMs,
|
|
41
41
|
http_timeout_ms: context.config.diagnostics.httpTimeoutMs,
|
|
42
|
-
overall_operation_timeout_ms: context.config.diagnostics.overallOperationTimeoutMs
|
|
42
|
+
overall_operation_timeout_ms: context.config.diagnostics.overallOperationTimeoutMs,
|
|
43
|
+
native_request_timeout: context.config.diagnostics.nativeRequestTimeout
|
|
43
44
|
}
|
|
44
45
|
},
|
|
45
46
|
diagnostics: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildServerInfo.js","sourceRoot":"","sources":["../../src/serverInfo/buildServerInfo.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAI3D,MAAM,UAAU,eAAe,CAAC,OAA6B,EAAE,kBAA+C;IAC5G,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAEzC,OAAO;QACL,WAAW,EAAE,QAAQ;QACrB,cAAc,EAAE,WAAW;QAC3B,WAAW,EAAE,OAAO,CAAC,UAAU;QAC/B,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,YAAY,EAAE,WAAW;QACzB,SAAS,EAAE;YACT,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC;YAC7E,WAAW,EAAE,OAAO,EAAE,UAAU,IAAI,IAAI;YACxC,WAAW,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI;YAC5C,YAAY,EAAE,OAAO,EAAE,eAAe,IAAI,IAAI;YAC9C,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,IAAI,IAAI;YACzD,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,IAAI,IAAI;SAC1D;QACD,YAAY,EAAE;YACZ,WAAW,EAAE,MAAM;YACnB,2BAA2B,EAAE,CAAC,wBAAwB,EAAE,iBAAiB,CAAC;YAC1E,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YACtC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW;SACzC;QACD,MAAM,EAAE;YACN,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa;YAC7C,4BAA4B,EAAE,OAAO,CAAC,MAAM,CAAC,yBAAyB,IAAI,IAAI;YAC9E,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB;YACpD,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc;YAC/C,6BAA6B,EAAE,OAAO,CAAC,MAAM,CAAC,yBAAyB;YACvE,gCAAgC,EAAE,OAAO,CAAC,MAAM,CAAC,4BAA4B;YAC7E,uBAAuB,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB;YAC5D,WAAW,EAAE;gBACX,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,gBAAgB;gBAC/D,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY;gBACvD,sBAAsB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,mBAAmB;gBACtE,4BAA4B,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,yBAAyB;gBAClF,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,iBAAiB;gBAChE,qBAAqB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,kBAAkB;gBACpE,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,gBAAgB;gBAC/D,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa;gBACzD,4BAA4B,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,yBAAyB;
|
|
1
|
+
{"version":3,"file":"buildServerInfo.js","sourceRoot":"","sources":["../../src/serverInfo/buildServerInfo.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAI3D,MAAM,UAAU,eAAe,CAAC,OAA6B,EAAE,kBAA+C;IAC5G,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAEzC,OAAO;QACL,WAAW,EAAE,QAAQ;QACrB,cAAc,EAAE,WAAW;QAC3B,WAAW,EAAE,OAAO,CAAC,UAAU;QAC/B,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,YAAY,EAAE,WAAW;QACzB,SAAS,EAAE;YACT,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC;YAC7E,WAAW,EAAE,OAAO,EAAE,UAAU,IAAI,IAAI;YACxC,WAAW,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI;YAC5C,YAAY,EAAE,OAAO,EAAE,eAAe,IAAI,IAAI;YAC9C,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,IAAI,IAAI;YACzD,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,IAAI,IAAI;SAC1D;QACD,YAAY,EAAE;YACZ,WAAW,EAAE,MAAM;YACnB,2BAA2B,EAAE,CAAC,wBAAwB,EAAE,iBAAiB,CAAC;YAC1E,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YACtC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW;SACzC;QACD,MAAM,EAAE;YACN,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa;YAC7C,4BAA4B,EAAE,OAAO,CAAC,MAAM,CAAC,yBAAyB,IAAI,IAAI;YAC9E,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB;YACpD,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc;YAC/C,6BAA6B,EAAE,OAAO,CAAC,MAAM,CAAC,yBAAyB;YACvE,gCAAgC,EAAE,OAAO,CAAC,MAAM,CAAC,4BAA4B;YAC7E,uBAAuB,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB;YAC5D,WAAW,EAAE;gBACX,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,gBAAgB;gBAC/D,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY;gBACvD,sBAAsB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,mBAAmB;gBACtE,4BAA4B,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,yBAAyB;gBAClF,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,iBAAiB;gBAChE,qBAAqB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,kBAAkB;gBACpE,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,gBAAgB;gBAC/D,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa;gBACzD,4BAA4B,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,yBAAyB;gBAClF,sBAAsB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,oBAAoB;aACxE;SACF;QACD,WAAW,EAAE;YACX,QAAQ,EAAE,kBAAkB,IAAI,IAAI;SACrC;QACD,OAAO,EAAE;YACP,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI;YACnC,mBAAmB,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS;SAC1D;QACD,GAAG,EAAE;YACH,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS;SAC1D;KACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,QAA4B;IACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC"}
|
package/docs/CHECKPOINT.md
CHANGED
|
@@ -23,8 +23,6 @@ The server is a test-backed, read-only MCP service for Arista EOS eAPI. The prod
|
|
|
23
23
|
- `validate-inventory`
|
|
24
24
|
- `print-server-info`
|
|
25
25
|
- `probe`
|
|
26
|
-
- `inspect-diagnostics` (read-only persisted state and audit, no device access)
|
|
27
|
-
- `release-diagnostics` (offline operator attestation, explicit scope and atomic audit)
|
|
28
26
|
|
|
29
27
|
## Safety and operational controls
|
|
30
28
|
|
|
@@ -35,10 +33,9 @@ The server is a test-backed, read-only MCP service for Arista EOS eAPI. The prod
|
|
|
35
33
|
- HTTPS with certificate validation by default and optional custom CA support.
|
|
36
34
|
- Validated show commands and bounded logging/running-configuration queries.
|
|
37
35
|
- Read-operation concurrency, target, response-size, and timeout controls.
|
|
38
|
-
- Configurable diagnostic budgets,
|
|
39
|
-
- Write-ahead SQLite reservations, exact wire-ID persistence/correlation, exclusive crash-released ownership, restoration of unfinished work as uncertain, and fail-closed storage errors.
|
|
36
|
+
- Configurable diagnostic budgets, in-memory per-server admission, independent timeouts, and uncertain reservations held for the command budget after unknown remote completion (ADR 0006).
|
|
40
37
|
- JSON-RPC version/ID and result/error exclusivity validation precedes output interpretation or read-only fallback; unrelated acknowledgements cannot free diagnostic capacity.
|
|
41
|
-
- Live sanitized capacity/reservation visibility in server info
|
|
38
|
+
- Live sanitized capacity/reservation visibility in server info, including when each uncertain slot frees. A qualified native remote kill/deadline mechanism remains unimplemented; installed EOS requestTimeout is not an end-to-end termination guarantee.
|
|
42
39
|
- No retries or shell/SSH fallback for ping. Non-configuring diagnostics are not passive reads; traceroute and capture are not implemented.
|
|
43
40
|
- Sanitized result envelopes with stable machine-readable errors.
|
|
44
41
|
|
|
@@ -53,4 +50,4 @@ The server is a test-backed, read-only MCP service for Arista EOS eAPI. The prod
|
|
|
53
50
|
|
|
54
51
|
Run `npm test`, `npm run typecheck`, `npm run lint`, and `npm run build` after changes. Optional integration tests require the configured cEOS environment. Ping's module and MCP stdio integration suites are qualified on cEOS 4.34.3M; see [DIAGNOSTICS.md](DIAGNOSTICS.md).
|
|
55
52
|
|
|
56
|
-
Latest
|
|
53
|
+
Latest verification (in-memory capacity, ADR 0006): unit suite, typecheck, static policy and build pass; `INTEGRATION=1` was run against the cEOS lab, including a second server pinging the same inventory while the first held an uncertain slot.
|
package/docs/CODE_WALKTHROUGH.md
CHANGED
|
@@ -20,8 +20,6 @@ Execution starts at `src/index.ts`. It parses CLI args, loads config, then dispa
|
|
|
20
20
|
- `validate-inventory`: validate inventory and print the result.
|
|
21
21
|
- `print-server-info`: print sanitized server metadata.
|
|
22
22
|
- `probe`: run the same probe logic as the MCP probe tool.
|
|
23
|
-
- `inspect-diagnostics`: read existing reservations and release audit without device access, ownership takeover, or reset.
|
|
24
|
-
- `release-diagnostics`: offline, exact-scope operator release with explicit fencing attestation and atomic audit; no device calls or MCP exposure.
|
|
25
23
|
|
|
26
24
|
CLI parsing lives in `src/cli.ts`. The app name and version are read from `package.json` at runtime via `src/core/version.ts`. Shared error handling lives in `src/core/errors.ts`, which defines `AppError` and the stable machine-readable error codes used across the CLI and MCP layers.
|
|
27
25
|
|
|
@@ -53,7 +51,7 @@ eAPI protocol and transport are split:
|
|
|
53
51
|
|
|
54
52
|
- `src/eapi/client.ts`: builds JSON-RPC `runCmds` requests using the connection's `eapiVersion` (`"latest"` by default, `1` when pinned in config), handles HTTP errors, invalid JSON, protocol version/response-ID correlation, exclusive result/error envelopes, JSON-RPC errors, and unexpected result counts. Supports opt-in enable mode (`{ enable: true }`) for privileged commands by prepending `enable` to the wire commands and stripping the extra result entry transparently. Per-request timeout and caller cancellation are combined with `AbortSignal`.
|
|
55
53
|
- `src/eapi/transport.ts`: production HTTPS transport and test-friendly fetch transport. The production transport enforces `maxResponseSizeBytes` while chunks are received, before buffering an oversized device response.
|
|
56
|
-
- `src/eapi/client.ts` also exposes single-execution text commands for active diagnostics, without JSON fallback retries, using the
|
|
54
|
+
- `src/eapi/client.ts` also exposes single-execution text commands for active diagnostics, without JSON fallback retries, using the reserved request identity supplied by the diagnostic execution context.
|
|
57
55
|
- `src/eapi/types.ts`: shared eAPI types, the low-level `EosCommandRunner` interface used by `EapiDeviceReader`, `EapiCommandOptions`, and helpers for validating/drilling into eAPI JSON-RPC response payloads.
|
|
58
56
|
|
|
59
57
|
## Read Tool Services
|
|
@@ -77,17 +75,15 @@ Shared type-narrowing utilities (`isObject`, `readString`, `readBoolean`, `readN
|
|
|
77
75
|
## Diagnostic Execution
|
|
78
76
|
|
|
79
77
|
- `src/diagnostics/executor.ts`: typed diagnostic orchestration, inventory opt-in and probe-budget checks, existing result envelopes, and cancellation. One executor is created per server instance.
|
|
80
|
-
- `src/diagnostics/concurrency.ts`: admission shared by concurrent calls
|
|
78
|
+
- `src/diagnostics/concurrency.ts`: in-memory admission shared by concurrent calls in one server process. Unknown completion holds a slot for `commandTimeoutMs`, then a timer frees it; a late confirmed response frees it sooner. Shutdown rejects queued work; cancelled waiters are removed.
|
|
81
79
|
- `src/diagnostics/eapiRunner.ts`: connection resolution followed by one text eAPI attempt in enable mode. Separate error paths distinguish local rejection before dispatch from uncertain RPC, transport, or malformed-result failures after dispatch; there is no mutable dispatch flag or retry.
|
|
82
80
|
- `src/diagnostics/executionError.ts`: typed `AppError` classification (`not_dispatched`, `completed`, or `unknown`). Only explicitly settled errors permit capacity reuse; ordinary errors remain uncertain.
|
|
83
81
|
- `src/diagnostics/ping.ts`: literal-address/VRF/source/count validation, finite ping command construction, and loss/RTT interpretation. A no-reply finding is not an execution failure.
|
|
84
82
|
- `src/diagnostics/types.ts`: request, prepared intent, observation, and finding types discriminated by diagnostic kind, plus the `DiagnosticDeviceRunner` seam. Only ping is implemented; capture sessions are not pre-modeled.
|
|
85
83
|
|
|
86
|
-
Diagnostic execution reuses read-operation orchestration with independent settings and global admission. Unknown completion
|
|
84
|
+
Diagnostic execution reuses read-operation orchestration with independent settings and global admission. Unknown completion holds capacity for the command budget; HTTP abort is not a remote kill signal. Each server-info call reads `DiagnosticExecutor.getCapacity()` afresh; the standalone CLI reports no live snapshot.
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`releaseEvidence.ts` validates attestation scope/consistency and uses shared JSON SHA-256 hashing for evidence and audit checksums; it does not verify external fencing. `reservationSchema.ts` validates and normalizes both unresolved records and audit snapshots, including absent legacy RPC IDs. The refactor preserves serialized records, checksum computation, and schema versions. Schema-1 migration, audit insertion, and selected deletions share the release transaction; ordinary server migration preserves occupied capacity. See [DIAGNOSTICS.md](DIAGNOSTICS.md).
|
|
86
|
+
Capacity lives in memory per server process, so several servers can run against one inventory. See [DIAGNOSTICS.md](DIAGNOSTICS.md) and [ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md).
|
|
91
87
|
|
|
92
88
|
## MCP Layer
|
|
93
89
|
|
|
@@ -126,11 +122,10 @@ Tests mirror the source layout:
|
|
|
126
122
|
- Inventory and target behavior: `inventory.test.ts`, `targetResolution.test.ts`
|
|
127
123
|
- eAPI and connection behavior: `eapiClient.test.ts`, `eosDeviceReader.test.ts`, `startupValidation.test.ts`, `responseSize.test.ts`
|
|
128
124
|
- Read services and orchestration: `probeDevices.test.ts`, `runShow.test.ts`, `loggingQuery.test.ts`, `showLogging.test.ts`, `getFacts.test.ts`, `getRunningConfig.test.ts`, `readExecution.test.ts`
|
|
129
|
-
- Diagnostics: `ping.test.ts`, `diagnosticConfig.test.ts`, `diagnostics.test.ts`, `diagnosticEapi.test.ts`, `diagnosticReservations.test.ts` (
|
|
130
|
-
-
|
|
125
|
+
- Diagnostics: `ping.test.ts`, `diagnosticConfig.test.ts`, `diagnostics.test.ts`, `diagnosticEapi.test.ts`, `diagnosticReservations.test.ts` (timed release, cancellation, late responses and visibility)
|
|
126
|
+
- Response correlation: `eapiCorrelation.test.ts`
|
|
131
127
|
- Server info and errors: `buildServerInfo.test.ts`, `errors.test.ts`, `errorTaxonomy.test.ts`
|
|
132
128
|
- MCP adapters and stdio: `readToolsMcp.test.ts`, `probeDevicesTool.test.ts`, `listInventoryTool.test.ts`, `mcpStdioSmoke.test.ts`
|
|
133
129
|
- Package/static policy: `packagePolicy.test.ts`, `packageSmoke.test.ts`
|
|
134
130
|
- Optional cEOS integration (requires a configured lab): `integration.test.ts`, `pingIntegration.test.ts`, `pingMcpIntegration.test.ts`
|
|
135
|
-
- Offline attested release and CLI: `diagnosticRelease.test.ts`, `releaseDiagnosticsCli.test.ts` (synthetic evidence only; no live EOS releases)
|
|
136
131
|
- Shared test helpers: `test/helpers.ts`
|
package/docs/DESIGN.md
CHANGED
|
@@ -52,15 +52,15 @@ Read requests have configured per-device timeout, optional overall timeout, devi
|
|
|
52
52
|
|
|
53
53
|
### Active diagnostic controls
|
|
54
54
|
|
|
55
|
-
`eos_ping` uses independent `diagnostics` settings with finite defaults: count 3/max 10, aggregate probes 100 (hosts × destinations × count), 16 destinations per call, server
|
|
55
|
+
`eos_ping` uses independent `diagnostics` settings with finite defaults: count 3/max 10, aggregate probes 100 (hosts × destinations × count), 16 destinations per call, per-server device concurrency 5, reply timeout 1000 ms, command budget 30000 ms, HTTP timeout 35000 ms, and overall timeout 60000 ms. All are configurable; target and response-size limits are shared with reads. Config validation requires consistent counts, execution budgets, and HTTP timeouts.
|
|
56
56
|
|
|
57
|
-
A ping request names one or more destinations and sends exactly one text-mode eAPI request per host and destination, without automatic retries. Those executions run concurrently under the shared admission controller, each with its own reservation, and return one result per host and destination. Fixed count, one-second interval, fixed size 64, and reply timeout bound device work;
|
|
57
|
+
A ping request names one or more destinations and sends exactly one text-mode eAPI request per host and destination, without automatic retries. Those executions run concurrently under the shared admission controller, each with its own reservation, and return one result per host and destination. Fixed count, one-second interval, fixed size 64, and reply timeout bound device work; by default each request also carries EOS `requestTimeout` set to the command budget (`diagnostics.nativeRequestTimeout`), which interrupts execution on the device but does not cover time queued before execution. HTTP abort is not proof of remote completion. Unknown completion holds its admission slot for the command budget, then frees it; a late confirmed response frees it sooner. Capacity is held in memory per server process, so several servers can use one inventory at once ([ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md)). Server info exposes current sanitized capacity and reservation state. A qualified native remote deadline remains unresolved; timeout-shaped RPC errors are not assumed to confirm process exit. Cancellation stops scheduling and aborts HTTP requests; overall timeout remains a request-level failure without partial results. See [DIAGNOSTICS.md](DIAGNOSTICS.md) for the full lifecycle assumptions and limits.
|
|
58
58
|
|
|
59
59
|
Only unicast literal destinations are supported; DNS, scoped/link-local IPv6, multicast, unspecified, and limited broadcast addresses are rejected. Optional VRF/source selection must not fall back. Directed-broadcast detection is not guaranteed from an address alone.
|
|
60
60
|
|
|
61
61
|
## Result and error model
|
|
62
62
|
|
|
63
|
-
Every eAPI response must identify JSON-RPC 2.0, match its request ID exactly, and contain exactly one result/error member before interpretation. Missing or mismatched identity is `eapi_response_uncorrelated`, not completion evidence.
|
|
63
|
+
Every eAPI response must identify JSON-RPC 2.0, match its request ID exactly, and contain exactly one result/error member before interpretation. Missing or mismatched identity is `eapi_response_uncorrelated`, not completion evidence. Each diagnostic execution's reservation ID is its wire ID. Correlation IDs do not provide an EOS job lookup, cancellation, or idempotency facility.
|
|
64
64
|
|
|
65
65
|
Device-facing tools return target metadata, resolved devices, summary counts, and per-device results. Request-level failures and known device failures use `AppError` machine-readable codes. Supported read tools expose raw eAPI payloads only through an explicit `include_raw` option. Ping separates successful execution from a reachability finding and returns normalized loss/RTT measurements; original EOS text is opt-in. Unrecognized output is an explicit device failure, never an invented finding.
|
|
66
66
|
|
package/docs/DIAGNOSTICS.md
CHANGED
|
@@ -63,6 +63,7 @@ diagnostics:
|
|
|
63
63
|
commandTimeoutMs: 30000
|
|
64
64
|
httpTimeoutMs: 35000
|
|
65
65
|
overallOperationTimeoutMs: 60000
|
|
66
|
+
nativeRequestTimeout: true
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
| Setting | Meaning |
|
|
@@ -71,13 +72,14 @@ diagnostics:
|
|
|
71
72
|
| `maxPingCount` | Maximum probes per device and destination |
|
|
72
73
|
| `maxProbesPerRequest` | Maximum resolved host count × destination count × requested probes |
|
|
73
74
|
| `maxDestinationsPerRequest` | Maximum destinations in one `eos_ping` call |
|
|
74
|
-
| `deviceConcurrency` |
|
|
75
|
+
| `deviceConcurrency` | Diagnostic capacity of one server process, shared by its active and uncertain executions across calls |
|
|
75
76
|
| `pingReplyTimeoutMs` | EOS per-reply timeout; a positive multiple of 1000 ms |
|
|
76
77
|
| `commandTimeoutMs` | Conservative device execution budget; see lifecycle below |
|
|
77
78
|
| `httpTimeoutMs` | Per-device diagnostic eAPI HTTP timeout, separate from `readTimeoutMs` |
|
|
78
79
|
| `overallOperationTimeoutMs` | Whole diagnostic call deadline, including waiting for global capacity |
|
|
80
|
+
| `nativeRequestTimeout` | Send EOS `runCmds.requestTimeout` set to `commandTimeoutMs`, so the device interrupts a ping that outlives its budget; set `false` for an EOS version that rejects the field |
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
Diagnostic capacity is held in memory by each server process; nothing is written to disk. Several MCP servers (for example two chat sessions) can run against the same inventory at once, each with its own `deviceConcurrency`. The former `diagnostics.stateFile` setting is rejected with guidance; delete it. Ledger files left under `$XDG_STATE_HOME/eos-mcp-server/` (or `~/.local/state/eos-mcp-server/`) by earlier versions are no longer read and can be deleted. See [ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md).
|
|
81
83
|
|
|
82
84
|
Existing `maxReadTargets` (default 50) and `maxResponseSizeBytes` (default 1,048,576) also apply. Diagnostic concurrency and timeouts are independent of the corresponding read settings. Effective diagnostic settings are reported by `eos_get_server_info` under `limits.diagnostics`.
|
|
83
85
|
|
|
@@ -91,84 +93,33 @@ The HTTP timeout must exceed the command budget by at least 1000 ms. Individual
|
|
|
91
93
|
|
|
92
94
|
## Execution and lifecycle
|
|
93
95
|
|
|
94
|
-
Each host/destination execution is one text-mode eAPI request containing `enable` followed by the generated ping command. The
|
|
96
|
+
Each host/destination execution is one text-mode eAPI request containing `enable` followed by the generated ping command. The reservation ID is used as the exact wire request ID. All eAPI responses, including read-tool responses, must use JSON-RPC 2.0, match the exact request ID, and contain exactly one `result` or `error` member. Missing/stale identity fails with `eapi_response_uncorrelated`; contradictory envelopes fail with `eapi_payload_invalid`. Neither is completion evidence, and an unrelated response is never exposed as the requested diagnostic's raw output. There are **no automatic ping retries**, including no JSON-to-text retry. Read-only auto fallback also requires a correlated error and uses a new ID for its subsequent request. Existing `eos_run_show` validation remains show-only.
|
|
95
97
|
|
|
96
|
-
On cEOS 4.34.3M, ping's `timeout` maps to a per-reply wait, not a whole-process deadline. The tool combines finite `repeat`, explicit one-second `interval`, and finite reply timeout; the conservative execution formula above must fit `commandTimeoutMs`. This budget is **not** a remote wall-clock kill timer. It assumes normal EOS command scheduling, not a stalled operating system or an indefinitely queued eAPI command. No remote process termination guarantee is made.
|
|
98
|
+
On cEOS 4.34.3M, ping's `timeout` maps to a per-reply wait, not a whole-process deadline. The tool combines finite `repeat`, explicit one-second `interval`, and finite reply timeout; the conservative execution formula above must fit `commandTimeoutMs`. This budget is **not** a remote wall-clock kill timer. It assumes normal EOS command scheduling, not a stalled operating system or an indefinitely queued eAPI command. No remote process termination guarantee is made. The same budget is how long an uncertain slot is held (below).
|
|
97
99
|
|
|
98
|
-
One admission controller is shared by all diagnostic calls
|
|
99
|
-
|
|
100
|
-
The ledger uses Node's built-in SQLite (Node 24+, potentially marked experimental by the runtime), FULL synchronous commits, and a separate SQLite ownership database. Only one server may own a ledger; another owner fails with `diagnostic_state_in_use`. OS locking releases ownership when a process crashes, without PID-age guessing or deleting a stale lock. All unfinished records, including records persisted as active before a crash, reopen as uncertain. Corrupt, incompatible, inventory-mismatched, or half-missing state is rejected rather than initialized as fresh capacity. A full SQLite integrity check runs before restored capacity is accepted and during offline inspection; malformed indexes must not hide occupied reservations. State write failure blocks further diagnostics with `diagnostic_state_write_failed`; an abort listener does not throw if writing uncertainty fails, because the earlier committed record still preserves the reservation.
|
|
100
|
+
One admission controller is shared by all diagnostic calls in a server process. A slot is reserved **before** the device runner can dispatch. Confirmed synchronous completion or a locally established rejection before dispatch frees it immediately. Unknown completion after transport loss, HTTP timeout, invalid eAPI payload (including correlated results with missing or non-text output), cancellation, or a post-dispatch RPC error becomes **uncertain**: the slot stays held for `commandTimeoutMs` from that moment, then frees itself. A late confirmed response frees it sooner. RPC interruption errors are not treated as proof of completion. Restarting the server forgets uncertain slots; under normal scheduling the original command has finished within `commandTimeoutMs` of dispatch anyway. If EOS ever queued a request for longer than that, replacement work could overlap it; [ADR 0006](adr/0006-in-memory-timed-diagnostic-capacity.md) accepts that bounded risk for ping.
|
|
101
101
|
|
|
102
102
|
A request fans out into one execution per resolved host and destination. Executions run concurrently, up to `deviceConcurrency`, through the same admission controller as every other call; each takes and releases its own reservation exactly as a single ping does. Executions are scheduled host by host, in destination order. Queued executions wait for capacity inside the same overall deadline, so size multi-destination requests so that roughly `ceil(executions / available capacity)` successive waves of pings fit within `overallOperationTimeoutMs`; uncertain reservations reduce available capacity. The device can also limit overlap: on cEOS 4.34.3M, eAPI executed at most three commands concurrently per device, and further delivered requests waited on the device while holding their server reservations. A single-host request therefore gains at most that device's eAPI concurrency, even when more server capacity is free.
|
|
103
103
|
|
|
104
|
-
The overall deadline or MCP cancellation stops scheduling new executions, removes capacity waiters, and aborts in-flight HTTP work. Every in-flight execution of a cancelled multi-destination request becomes uncertain, so one cancellation can hold up to `deviceConcurrency` slots
|
|
104
|
+
The overall deadline or MCP cancellation stops scheduling new executions, removes capacity waiters, and aborts in-flight HTTP work. Every in-flight execution of a cancelled multi-destination request becomes uncertain, so one cancellation can hold up to `deviceConcurrency` slots for up to `commandTimeoutMs`; queued executions of that request are never dispatched. An overall timeout is a request-level `operation_timeout`, not a partial-results response. Already completed findings are not returned on that path. Do not blindly retry an interrupted diagnostic.
|
|
105
105
|
|
|
106
106
|
## Uncertain execution and capacity visibility
|
|
107
107
|
|
|
108
|
-
`eos_get_server_info` returns a fresh `diagnostics.capacity` snapshot with:
|
|
108
|
+
`eos_get_server_info` returns a fresh `diagnostics.capacity` snapshot for that server process with:
|
|
109
109
|
|
|
110
|
-
- `
|
|
110
|
+
- `limit` and `available_count`.
|
|
111
111
|
- `active_count`, `uncertain_count`, and `queued_count`.
|
|
112
|
-
- `reservations`: ID, inventory hostname, diagnostic kind, state, reservation timestamp, and (when uncertain) uncertainty timestamp and machine-readable reason code.
|
|
113
|
-
- `storage_error_code` when journal I/O has failed; new work is blocked even if the numeric limit would otherwise leave space.
|
|
112
|
+
- `reservations`: ID (also the wire RPC ID), inventory hostname, diagnostic kind, state, reservation timestamp, and (when uncertain) uncertainty timestamp, `releases_at`, and machine-readable reason code. Destinations, endpoints, credentials, and original EOS output are not included.
|
|
114
113
|
|
|
115
|
-
|
|
114
|
+
Uncertain slots count against `deviceConcurrency` like active ones. New work queues behind them and is dispatched when a slot frees, within the overall deadline; if the deadline expires first the call fails with `operation_timeout`. If some capacity remains, diagnostics keep running without waiting for uncertain slots.
|
|
116
115
|
|
|
117
116
|
The standalone `print-server-info` CLI has no live executor and reports `diagnostics.capacity: null`, not fabricated available capacity. Inspect the running MCP server for actual reservation state.
|
|
118
117
|
|
|
119
|
-
### Offline inspection and operator recovery
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
eos-mcp-server inspect-diagnostics --config eos-mcp-server.yml --json
|
|
123
|
-
# Or derive the default ledger from the same inventory identity:
|
|
124
|
-
eos-mcp-server inspect-diagnostics --inventory inventory.yml --json
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Inspection opens existing state read-only, including while another server owns it; it does not resolve credentials, contact devices, take over ownership, create a missing ledger, or clear records. Integrity, schema, reservations, and audit are read in one transaction, so a concurrent commit cannot mix before/after release states. A competing writer may fail closed while this short read snapshot is held; inspect current state before retrying an administrative release. Inspection reports recorded state, not proof that remote work completed.
|
|
128
|
-
|
|
129
|
-
**Operational constraints:** use a persistent local filesystem that honors SQLite locking and sync, private owner-controlled state files (POSIX mode 0600), and an owner-controlled directory not writable by group/others. Network filesystems and ephemeral/container storage are not qualified durability mechanisms. Preserve the ledger, its `.lock.sqlite` ownership file, and any SQLite recovery files together. Do not delete, replace, copy, rotate, roll back, or change the ledger path to clear reservations. Loss of both ledger files cannot be distinguished locally from first initialization. Before upgrading from the earlier in-memory implementation, unresolved old executions require independent reconciliation; they cannot be recovered from a journal that did not yet exist.
|
|
130
|
-
|
|
131
|
-
Restarting with the same ledger retains uncertainty. Recovery is now available only as an **offline, audited Operator Release** backed by a **Fencing Attestation**. There is no MCP reset, release-all operation, native completion-token lookup, or automatic timeout-based release. Stopping the MCP process establishes local ownership transfer, not remote termination.
|
|
132
|
-
|
|
133
|
-
#### Operator release workflow
|
|
134
|
-
|
|
135
|
-
1. Stop the server owning this ledger and prevent diagnostic resubmission. The release command refuses a live ledger owner; it does not stop one.
|
|
136
|
-
2. Run `inspect-diagnostics --json` and retain the `inventory_identity`, exact selected reservation IDs, immutable host/admission timestamps, and recorded RPC IDs. Establish the original physical device and execution epoch from independent evidence/history, not just today's inventory mapping.
|
|
137
|
-
3. Independently verify that the fence terminates running work, invalidates **accepted queued** work, and prevents later dispatch/replay. A timeout, empty process list, quiet counters, or repeated observations are insufficient.
|
|
138
|
-
4. Prepare a private JSON attestation from [the deliberately invalid template](examples/diagnostic-release-evidence.template.json). Replace every placeholder with actual evidence. Change each assertion to true only after verification. Do not put credentials, raw packet contents, or EOS output in the file; use evidence references instead.
|
|
139
|
-
5. Release only the exact covered IDs, then inspect the audit:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
# Prepare with private permissions; the template cannot authorize a release as-is.
|
|
143
|
-
umask 077
|
|
144
|
-
cp docs/examples/diagnostic-release-evidence.template.json ./evidence.json
|
|
145
|
-
# Human: complete the attestation from independently verified fencing evidence.
|
|
146
|
-
chmod 600 ./evidence.json
|
|
147
|
-
|
|
148
|
-
eos-mcp-server release-diagnostics --config eos-mcp-server.yml \
|
|
149
|
-
--reservation-id '<exact-id>' --evidence-file ./evidence.json \
|
|
150
|
-
--confirm-fenced --json
|
|
151
|
-
# Repeat --reservation-id only for other IDs covered by the same attestation.
|
|
152
|
-
eos-mcp-server inspect-diagnostics --config eos-mcp-server.yml --json
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
`--confirm-fenced`, an evidence file, and explicit unique IDs are mandatory. Evidence is private, owner-controlled UTF-8 JSON with unique keys, at most 65536 bytes and 100 explicitly covered reservations. There is no wildcard selection. The command performs no credential resolution, device call, URI fetch, fencing action, or network retry. A state-only config with an explicit `diagnostics.stateFile` can be used if the original inventory is unavailable; the attestation still must match the ledger's stored inventory identity. If config declares an inventory path, its canonical identity must also match.
|
|
156
|
-
|
|
157
|
-
The attestation requires `evidence_version: 1`, a new UUID `attestation_id`, the exact `inventory_identity`, an accountable `verified_by`, a past UTC `verified_at`, a reason, and four literal-true assertions: `independent_evidence_verified`, `running_work_terminated`, `accepted_queued_work_cannot_execute`, and `replay_prevented`. Each covered reservation must match its exact ID, host and `reserved_at`, and its exact `rpc_request_id` (explicit JSON null for legacy records without one). It must also identify the physical device, fenced execution epoch, fencing time, and independent evidence reference. Fencing must follow reservation admission and precede verification. The selected CLI IDs and evidence scope must match exactly.
|
|
158
|
-
|
|
159
|
-
#### Audit and authority
|
|
160
|
-
|
|
161
|
-
The release audit and every selected deletion commit in **one FULL-synchronous SQLite transaction** under exclusive ledger ownership. A failed batch rolls back earlier deletions and its audit; unselected reservations remain held. The append-only-through-the-application audit records original reservation snapshots, the complete attestation, its canonical SHA-256 digest, an audit-content checksum, server recording time, and actual local OS username/UID. `verified_by` is a claimed accountable person, not authenticated identity; the OS account is collected independently. Local operators must be trusted and protected by OS access controls. This is not a cryptographic human-presence check, and an untrusted agent with the ledger owner's filesystem privileges is outside that protection.
|
|
162
|
-
|
|
163
|
-
`inspect-diagnostics --json` exposes `release_audit` to local operators; MCP server info does not expose the evidence or audit. Checksums detect accidental inconsistency, not deliberate tampering by the trusted filesystem owner. The program validates consistency and explicit authority; it **cannot establish the truth of external fencing evidence**. Releasing on false attestation can violate remote concurrency safety.
|
|
164
|
-
|
|
165
|
-
If CLI completion is lost, inspect the audit first. Repeating the exact committed attestation returns its original release ID with `already_committed: true` and makes no additional changes. Reusing an attestation UUID for changed evidence is rejected. This local administrative idempotency does not make EOS ping requests idempotent; never retry a ping just because its RPC ID is known.
|
|
166
|
-
|
|
167
|
-
Ledger schema 1 upgrades atomically to schema 2, preserving unresolved reservations and adding release history. Read-only inspection of schema 1 does not migrate it. A rejected release also rolls back any migration. Schema-2 ledgers with missing or invalid audit history fail closed, and older schema-1-only binaries must not be used to bypass the audit.
|
|
168
|
-
|
|
169
118
|
### Native EOS timeout qualification
|
|
170
119
|
|
|
171
|
-
The installed cEOS implementation exposes a `runCmds.requestTimeout` execution-stage interruption timer. It was accepted in a bounded read request, and a later loopback experiment observed an interruption response after 273 ms with a 250 ms native timer (RPC code 1001 / error 408). No ping process appeared in the subsequent samples. This single normal-load result does not qualify a dispatch-relative or process-exit deadline, nor establish the public support contract.
|
|
120
|
+
The installed cEOS implementation exposes a `runCmds.requestTimeout` execution-stage interruption timer. It was accepted in a bounded read request, and a later loopback experiment observed an interruption response after 273 ms with a 250 ms native timer (RPC code 1001 / error 408). No ping process appeared in the subsequent samples. This single normal-load result does not qualify a dispatch-relative or process-exit deadline, nor establish the public support contract.
|
|
121
|
+
|
|
122
|
+
Ping requests now send `requestTimeout` equal to `commandTimeoutMs` (in seconds) by default, so EOS also stops a command that outlives its budget. Re-checked on the cEOS 4.34.3M lab: a 4-probe loopback ping with a 30 s timeout completed normally in about 3.1 s; with 0.25 s EOS returned RPC 1001 / error 408 after 264 ms and no ping process remained on the device. The timer starts only when EOS begins executing the command, so it does not cover time a request spends queued before that. A timeout-shaped RPC error is still treated as unknown completion and holds the slot like any other uncertain execution. Other EOS versions and physical platforms have not been checked; if one rejects the field, every ping fails with an RPC error, and `diagnostics.nativeRequestTimeout: false` turns it off. No durable token-bound status/cancel method was qualified. See [the primary-source investigation](research/eos-diagnostic-lifecycle.md) for evidence, citations, and limits.
|
|
172
123
|
|
|
173
124
|
## Results
|
|
174
125
|
|
|
@@ -187,7 +138,7 @@ Execution `status: "success"` means ping executed and its output was understood.
|
|
|
187
138
|
|
|
188
139
|
Each diagnostic kind supplies a `DiagnosticDefinition`: `prepare` validates the whole request before dispatch and returns the echoed request plus the executions every resolved host performs, `probeCount` prices one execution against the aggregate budget, and `interpret` turns observed output into a finding. The executor runs every prepared execution on every host and knows nothing kind-specific. Ping's definition, validation, compilation, and interpretation live in `src/diagnostics/ping.ts`; it prepares one execution per destination. Multiple destinations are therefore a property of ping's definition, not of the executor.
|
|
189
140
|
|
|
190
|
-
Admission queueing is separate from reservation
|
|
141
|
+
Admission queueing is separate from reservation state transitions; shutdown rejects queued work with `diagnostic_server_stopping`. The eAPI adapter separates connection validation from dispatch/error handling instead of maintaining a dispatch flag. `DiagnosticExecutionError` carries a typed `not_dispatched`, `completed`, or `unknown` classification; ordinary errors and malformed acknowledgements remain uncertain.
|
|
191
142
|
|
|
192
143
|
The request, observation, and finding types are discriminated by `kind`; only `ping` is implemented. Add other real operations at this seam without exposing arbitrary command strings. A destination-based operation such as traceroute can reuse multi-destination fan-out by preparing one execution per destination, although its much longer execution bound makes large requests hard to fit in the overall deadline. Packet capture needs a separate decision about transport, privilege, visibility, asynchronous lifetime, artifacts, and sensitive data; no capture-session abstractions are fabricated now.
|
|
193
144
|
|
|
@@ -198,15 +149,13 @@ Validated against the repository's cEOS 4.34.3M lab over HTTPS eAPI:
|
|
|
198
149
|
- IPv4/IPv6 loopback, explicit source and default VRF, and measured RTTs.
|
|
199
150
|
- Explicit one-second pacing, finite repeat count, and reply-timeout behavior.
|
|
200
151
|
- Group fan-out and 100% packet loss in the MGMT VRF.
|
|
201
|
-
- Multi-destination fan-out: six 10-packet pings from one host to its BGP peers completed in 18.6 s (about 56 s when issued as serial calls). A
|
|
152
|
+
- Multi-destination fan-out: six 10-packet pings from one host to its BGP peers completed in 18.6 s (about 56 s when issued as serial calls). A capacity sampler observed five simultaneous active reservations, and direct concurrent eAPI requests confirmed the device ran at most three commands at once.
|
|
202
153
|
- Invalid VRF and unavailable source fail without fallback.
|
|
203
154
|
- Overall HTTP cancellation can leave a short-lived device ping; observed processes exited after their finite probe sequence.
|
|
204
155
|
- MCP stdio execution loads configured defaults and returns real measurements.
|
|
205
156
|
|
|
206
157
|
Older EOS versions and physical platforms have not been qualified for this new tool; the existing read-path minimum does not establish ping compatibility.
|
|
207
158
|
|
|
208
|
-
The refactor was also checked against synthetic ledgers and release audits written by the pre-refactor code (`0e208ec`): inspection, checksum validation, exact committed retries, legacy absent RPC IDs, and remaining reservations stayed compatible. Audit snapshots use the shared reservation codec, and evidence/audit digests share the same JSON SHA-256 implementation; the serialized formats and schema versions are unchanged.
|
|
209
|
-
|
|
210
159
|
```bash
|
|
211
160
|
npm test
|
|
212
161
|
npm run typecheck
|
|
@@ -215,4 +164,4 @@ npm run build
|
|
|
215
164
|
INTEGRATION=1 npm test
|
|
216
165
|
```
|
|
217
166
|
|
|
218
|
-
Integration tests use the isolated `clab/testlab.clab.yaml` lab (admin/admin and lab-only TLS override). The read-path suite now defaults to that topology's unprefixed node names; `EOS_MCP_TEST_PREFIX=clab-testlab-` supports older prefixed deployments. All 13 read-path checks have been run alongside the ping suites, including the EOS code-1003 unconverted-command JSON-to-text read fallback; active ping still never retries. Optional `EOS_PING_TEST_HOST`, `EOS_PING_TEST_HOST_2`, and `EOS_PING_TEST_SILENT_HOST` override its device/silent-destination addresses. Unit tests additionally cover partial loss, input injection, mapped-address restrictions, fail-closed policy, invalid settings, concurrent calls, multi-destination ordering, concurrent destination dispatch, per-destination failure isolation, whole-request destination validation, multi-destination cancellation, uncertain reservations
|
|
167
|
+
Integration tests use the isolated `clab/testlab.clab.yaml` lab (admin/admin and lab-only TLS override). The read-path suite now defaults to that topology's unprefixed node names; `EOS_MCP_TEST_PREFIX=clab-testlab-` supports older prefixed deployments. All 13 read-path checks have been run alongside the ping suites, including the EOS code-1003 unconverted-command JSON-to-text read fallback; active ping still never retries. Optional `EOS_PING_TEST_HOST`, `EOS_PING_TEST_HOST_2`, and `EOS_PING_TEST_SILENT_HOST` override its device/silent-destination addresses. Unit tests additionally cover partial loss, input injection, mapped-address restrictions, fail-closed policy, invalid settings, concurrent calls, multi-destination ordering, concurrent destination dispatch, per-destination failure isolation, whole-request destination validation, multi-destination cancellation, uncertain reservations and their timed release, early release on a late confirmed response, cancellation, and response-size enforcement. Additional protocol regressions cover missing/stale IDs, invalid versions, contradictory result/error envelopes, and no retry on an uncorrelated read error. Live MCP integration checks timeout-to-uncertain visibility and runs a second server against the same inventory while the first holds an uncertain slot.
|
|
@@ -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,
|
|
16
|
-
- Local `validate-inventory`, `print-server-info`,
|
|
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.
|
|
@@ -4,6 +4,6 @@ Testing several destinations from the same target, such as every BGP peer of a s
|
|
|
4
4
|
|
|
5
5
|
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
6
|
|
|
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.
|
|
7
|
+
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
8
|
|
|
9
9
|
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,21 @@
|
|
|
1
|
+
# Replace the durable diagnostic ledger with in-memory, time-bounded capacity
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
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`.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
Accepted consequences:
|
|
12
|
+
|
|
13
|
+
- `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.
|
|
14
|
+
- A restart forgets uncertain slots. Under normal EOS scheduling the original command has finished within `commandTimeoutMs` of dispatch anyway.
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
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.
|
|
@@ -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.
|
package/package.json
CHANGED
|
@@ -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>;
|