Step 15: CLI and MCP #11
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
CLI and MCP with plans for AI agent tokens.
Step 15 in docs/PLAN.md. Done when: merged to dev
Starting step 15a (
cli-auth) on branchcli-authfrom basebfa8af7ffcc146d0d87a14e8bdcf5ed8434eefa4.Scope: RFC 8628 device authorization contract and implementation, server/storage/proto changes, one web approval route, CLI
login/logoutplus credential store/API helper, generated OpenAPI/client artifacts, and the requested defensive HTTP/unit/browser tests. The contract will be committed and posted before implementation.The required
emil-design-engandbetter-*UI skills are not installed on this VM/session; the UI work will followCLAUDE.mdanddocs/UI.mddirectly, and that limitation will be included in the report.Contract-authoring job started on branch cli-auth at required base
bfa8af7ffc. Scope is contract-only: docs/contracts.md §1.1 OAuth device authorization flow, associated §1.2 error registry and §4/§5 reconciliation. No implementation files, tests, migrations, or UI will be changed; implementation has not started.Step 15a contract authoring is complete on branch
cli-auth.Commit:
72c4b397d14c3768ad5fe58ce79ca18d63b6065eContract summary:
POST /api/v1/auth/device/authorization,/review,/approve,/deny, and/token, with exact behavioral request/response fields.slow_down+5-second behavior, terminal states, and atomic single issuance are normative.user_code(about 51.9 bits), the existing 16-byte public lookup id + 32-byte verifierdevice_codeconstruction, constant-time verification, process-keyed user-code lookup, bounded deterministic guessing limits, and no raw/code-derived values in persistent state, logs, traces, idempotency state, or audit.humantoken with the approving user effective-grant snapshot, returns the refresh credential in JSON, and uses existing refresh-family rotation/reuse revocation.authorization_pending,slow_down,access_denied,expired_token, anddevice_authorization_invalid; added device audit action names andda_correlation ids.Authoritative sources:
draft-ietf-oauth-v2-1-16: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-16Verification (
scripts/verify fast, verbatim):Source-link/diff sanity check (verbatim):
Implementation has not started. This commit changes
docs/contracts.mdonly.Starting cli-mcp on branch cli-mcp at base
65ba959. Scope: CLI resource commands, shared HTTP client/credentials seam, MCP stdio mode and required server-side MCP support, integration verification step, and ps benchmark. Auth/login, stream protocol, and unrelated lanes remain out of scope.Implementation start for step 15a. Branch: cli-auth. Base:
bfa8af7ffc. Current contract head:72c4b397d1. Scope: v3.4 device authorization proto/OpenAPI, bounded in-memory server flow and durable passkey-session provenance, /device approval UI, login/logout credential storage and authenticated API helper, plus server/CLI/browser tests and artifacts. I will not rebase or merge newer dev, will not push, and will keep CLI work outside login/logout/helper untouched. The required emil-design-eng and better-* UI skills are available in this run and have been loaded.Credential-store research evidence: current stable keyring is 4.2.0 (2026-08-29), with the default v1 Entry API exposing set/get/delete and reporting NoDefaultStore when initialization fails: https://docs.rs/crate/keyring/4.2.0 and https://docs.rs/keyring/4.2.0/keyring/v1/struct.Entry.html . On Linux its native-store helper defaults to kernel keyutils unless Secret Service is preferred, so this implementation will explicitly prefer Secret Service for persistent desktop keychain semantics: https://docs.rs/keyring/4.2.0/keyring/cli/fn.use_native_store.html . Freedesktop Secret Service documents that lookup attributes are non-secret and may be unencrypted, so deterministic record metadata will contain only the normalized server account key, never credentials or code-derived values: https://specifications.freedesktop.org/secret-service/latest-single/ . Durable notes are in artifacts/cli-auth/findings.md.
Finding: the existing engine contract removes only stopped containers (Docker returned HTTP 409 for a running target). The real CLI test therefore verifies the requested ps -> inspect -> planned stop -> start -> restart sequence, then performs a second planned stop before rm dry-run/execute. The corrected Docker+Podman CLI/MCP test passes in 7.25s; the live ps benchmark reports 350.98 us median in this VM (smoke only). Server-side resource routes already enforce plans and idempotency, so no auth or resource-route changes were needed.
Implementation evidence at head
26e61e14d9:b904a2fimplements strict device-auth proto/OpenAPI, bounded process-only authorization state, passkey-origin provenance, audited state transitions, JSON issuance, refresh-family revoke, and defensive auth/storage tests.0174b9badds login/logout, keyring 4.2.0 integration, restrictive atomic 0600 fallback, and credential-storage/logout tests.26e61e1adds the accessible /device route and extends real-binary Chromium coverage.Primary credential-store research: keyring 4.2.0 default v1 features select the zbus Secret Service backend on Linux (https://docs.rs/crate/keyring/4.2.0); Entry API and NoDefaultStore behavior are documented at https://docs.rs/keyring/4.2.0/keyring/v1/struct.Entry.html. Secret Service attributes are non-secret lookup metadata and may be stored unencrypted, so only the normalized server account key is metadata (https://specifications.freedesktop.org/secret-service/latest-single/). The keyring CLI native-store helper behavior is separately documented at https://docs.rs/keyring/4.2.0/keyring/cli/fn.use_native_store.html and is not invoked by infraternal.
No benchmark added: bounded device authorization is control-plane request handling, not a repository-defined per-frame/per-record/streaming hot path. Full fast/integration/browser gates and screenshots remain to run.
Finished cli-mcp on branch cli-mcp. Head:
eb14905063.Owned changes: resource CLI commands and shared HTTP client/credentials seam in crates/infraternal; MCP stdio mode with rmcp; real CLI/MCP integration coverage; and the ps server benchmark. Existing public resource routes already enforce auth, plans and idempotency, so no server-side MCP route or auth changes were needed. Login/logout, credential storage and stream protocol remain with their parallel/later jobs.
MCP research used the 2026-07-28 MCP specification and rmcp 3.4.0. The MCP test drives initialize and tools/list through rmcp, then stop_container dry_run=true -> plan_id -> execute over stdio. The CLI test starts the compiled server binary, registers a human through the real setup API, mints an AI agent token, uses real Docker and rootless Podman workloads, and checks ps/inspect/stop/start/restart/rm with JSON output. It never targets infraternal.demo=1.
Example CLI output (identifiers shortened; no credentials):
Benchmark smoke:
container_list_filter_500p50=47us, p99=126us; liveGET /api/v1/containersserver-backed ps median=348.66us (criterion range [339.40us 350.98us]).The first full integration attempt had one transient existing SSH owner_loss PTY acknowledgement timeout; the isolated test passed in 1.90s and the clean full rerun passed.
verify fast output, verbatim:
verify integration output, verbatim:
Starting independent review on branch
cli-mcpateb14905, against basedevat65ba959. Scope is review-only; no source edits or commits.Repair started on branch
cli-authat reviewed head26e61e14d904(basebfa8af7). Scope: cancellation-safe one-session device-token issuance plus exact reviewed-code binding in/device, with deterministic regression coverage and focused/fast verification. I will not push or close this issue.Independent review finished on
cli-mcpateb149050635b091b836ec0328053a4a8fc4860ff, againstdev65ba9593119510bf3b9727b3b2eadd8a1ea91bcd. Review-only; no source edits or commits.Findings:
[blocks merge] Mutation retries cannot reuse the idempotency key.
ApiClient::mutatecreates a new ULID inside every call and exposes no caller-selected key or retry state. A controlled two-call wire probe of identical CLI mutations capturedDIFFERENT_KEYS. After an unknown outcome, retrying a destructive command can dispatch it again. Add a real failure-injection test covering CLI and MCP same-key retry andoutcome_unknown.[blocks merge]
INFRATERNAL_TOKENis visible in process-environmentpsoutput.EnvironmentCredentialsreads the bearer from the process environment. A direct production-binary probe found a sentinel inps eww(LEAKS_TO_PS_EWW). It was not in argv, but the requirement coverspsoutput; same-user inspection can recover the bearer. Add assertions forps eww,/proc/<pid>/environ, stdout, stderr, JSON, and tracing.[blocks merge] The credential seam cannot plug in the parallel async refresh store without rework. This branch defines synchronous
CredentialProvider::access_token(&self)and always selectsEnvironmentCredentials; thecli-authbranch's durable store exposes an async mutable refresh path and rotates persisted refresh credentials. A controlled expired-token probe made one resource request, no refresh request, and exited3. With 15-minute access tokens, logged-in CLI/MCP processes stop working at expiry. Add a real login/expiry/rotation test before merging the lanes.[later] Plan terminal errors do not provide recovery guidance. Only
plan_requiredgets--dry-runguidance. Controlledplan_stale,plan_expired,plan_used, andplan_indeterminateresponses all exited6with no--dry-run,re-inspect, orre-planguidance. The indeterminate contract specifically requires re-inspection before a new plan. Exercise all four codes through CLI and MCP and make the next action explicit.[later]
--jsonis not JSON on errors and is undocumented.mainalways prints failures as human text; only successful command responses useprint_json. A controlled404 not_foundwithps --jsonproducedPLAIN_ERROR_OUTPUT_EXIT_5, droppingrequest_id,retryable, and typeddetails. Document and test the stable error envelope for every command.[later] MCP advertises no output schemas or tool behavior annotations. A raw 2026-07-28
tools/listprobe found input schemas for all seven tools, butoutputSchema: falseandannotations: nullfor all seven. Structured results therefore require an AI caller to infer response fields, and destructive/read-only status is prose-only. This is not a framing or error-shape failure: modernserver/discover,tools/list, and structuredplan_requiredtool errors passed. Add output schemas and behavior metadata or document the deliberate omission.[blocks merge] Integration coverage does not prove MCP refusal or plan safety edge cases. The real test checks CLI no-plan refusal and MCP only dry-run then execute. It does not call destructive MCP tools without plans, reuse plans with a new key, use another token, change parameters, expire a plan, or exercise indeterminate/unknown outcomes. A regression in MCP plan propagation can pass the current real-engine gate. Add real-server/real-binary cases for those refusal and retry paths.
[blocks merge] The
psbenchmark is neither a controlled 500-row benchmark nor the CLI/API path it claims. It opens a fresh temporary server without seeding 500 inventory rows, then benchmarksserver.router().oneshot, bypassing TCP, reqwest, and the compiled CLI. The verify label calls it “live ps server and 500-row container list,” so its smoke number cannot support that budget. The CLI itself makes one list request; no per-item round trips were found. On the perf-test VM, seed 500 rows and measure the claimed production path separately.Gate output (
scripts/verify fast):Gate output (
scripts/verify integration):Security repair finished on branch
cli-authata5a624d62f4252704470272dcb0a486fdf041808(fix(auth): make device issuance cancellation-safe).Resolved findings:
issuingunder the state lock before a detached task owns the durable transaction and final state change. Request cancellation drops only the response receiver; successful commit completesconsumedbefore credential delivery, definite failure restoresapproved, concurrent polls cannot reissue, andissuingremains within existing live-state bounds. Deterministic channel-driven tests cover cancellation at the commit boundary and safe failure retry./devicedecisions now bind to the exact reviewed input. Editing the code clears metadata and both decision controls, stale in-flight reviews are discarded, and decision requests use only the saved reviewed code. Real-browser coverage proves edited code B cannot be approved or denied from code A review.Focused test evidence:
scripts/verify fast:Focused production browser gate (
INFRATERNAL_DEVICE_ONLY=1 ... scripts/verify browser), at 1440x900 and 390x844:The full browser matrix was intentionally not run because the shared Docker/Podman daemons remain occupied by the unrelated benchmark load described in the job. The focused mode does not create or touch containers. Full evidence and reasoning are in
/mnt/hdd/infraternal/artifacts/cli-auth/fix-result.md; findings were appended tofindings.md. No push was performed and the issue remains open.Independent security review started for branch
cli-auth, basebfa8af7, candidatea5a624d62f4252704470272dcb0a486fdf041808. Review is read-only and covers the complete diff, RFC 8628/auth storage and issuance races, anti-phishing UI binding, generated API surfaces, tests, and lane conflicts.Verification job started on branch cli-auth at immutable head
a5a624d62f. Tracked worktree is clean. Running scripts/verify fast, integration, and browser sequentially; full logs and final report will be written under /mnt/hdd/infraternal/artifacts/cli-auth/.Security review result
Candidate
a5a624d62f4252704470272dcb0a486fdf041808is not ready to merge.Findings: 2 high, 4 medium, and 1 integration blocker.
http://URLs, so successful device, refresh, and revoke traffic can expose bearer/refresh credentials in plaintext.issuing.internalbut leave no failed issuance/poll audit record.main.rs.Positive verification: exact reviewed-code binding is sound; constant-time verifier checks and process-only code storage are present; the ordinary approved/issuing/consumed path did not reveal a double-issuance route; targeted auth/issuance/credential tests passed; generated OpenAPI and TypeScript schema are in sync.
Checks run: code-review-graph change/impact queries; full diff inspection;
git diff --check; 2 device HTTP tests, 2 issuance unit tests, and 5 credential-store tests (all passed); fresh OpenAPI and TypeScript schema drift comparisons (equal); controlled malformed-poll audit probe (25 requests produced 25 durable rows). Full repository gates were intentionally not run.Full report:
/mnt/hdd/infraternal/artifacts/cli-auth/security-review-final.mdStep 15a / issue #11 final verification
Result: PASS
cli-autha5a624d62f4252704470272dcb0a486fdf041808a5a624d62f4252704470272dcb0a486fdf041808fast,integration,browserTwo pre-existing untracked files were present and were not touched:
.codex-review-prompt.md.codex-verify-prompt.mdGate results
scripts/verify fast— PASSComplete log:
/mnt/hdd/infraternal/artifacts/cli-auth/gates/fast-final.logExact output:
scripts/verify integration— PASSComplete log:
/mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final.logExact output:
scripts/verify browser— PASSComplete log:
/mnt/hdd/infraternal/artifacts/cli-auth/gates/browser-final.logThe final browser run was the normal full gate.
INFRATERNAL_DEVICE_ONLYwas explicitly unset.INFRATERNAL_ARTIFACT_DIRwas set to/mnt/hdd/infraternal/artifacts/cli-authso the approval evidence landed in the issue-specific artifact directory.Exact output:
Device approval screenshots
Verified from the PNG headers:
/mnt/hdd/infraternal/artifacts/cli-auth/device-light-1440x900.png— 1440 x 900/mnt/hdd/infraternal/artifacts/cli-auth/device-dark-1440x900.png— 1440 x 900/mnt/hdd/infraternal/artifacts/cli-auth/device-light-390x844.png— 390 x 844/mnt/hdd/infraternal/artifacts/cli-auth/device-dark-390x844.png— 390 x 844Interference and open questions
scripts/verify browserprocess was found in another worktree.scripts/verify browserprocess was found in another worktree.scripts/browser-check.mjs's default artifact directory (/mnt/hdd/infraternal/artifacts/containers-fix) becauseINFRATERNAL_ARTIFACT_DIRwas unset. It was rerun as the normal full gate with the issue-specific artifact directory; the final requested log and screenshots are from that passing rerun.Final security repair started on branch cli-auth at base
a5a624d62f. Scope: bounded unauthenticated device polling/audit aggregation; CLI server-origin validation; fail-closed issuance supervision and expiry boundary; independent failure audit; proxy contract clarification; focused and fast verification. No push/close; cli-mcp remains read-only.Orchestrator ruling on
review-cli-mcp(headeb14905):INFRATERNAL_TOKENvisible throughps eww) is re-labelled later./proc/<pid>/environis readable only by the same user and root, which can already read the user's keychain session and files, and an environment token is the usual opt-in for automation (CI, AI agents). The default path is the keychain store fromcli-auth. Later work: document the variable as automation-only, preferINFRATERNAL_TOKEN_FILE(a path to a0600file), and never pass the token down to child processes.psbenchmark.--jsonerrors as structured JSON), 6 (MCPoutputSchemaand annotations).Plan: 15a (
cli-auth) and 15b sharecrates/infraternal, and finding 3 is the join between them. Oncecli-authfinishes and has its own independent review, both branches merge into one, and a single fix round covers both reviews. Then one verify, then merge todev.Final security repair finished on branch cli-auth at head
779353cb1d(basea5a624d62f).Commits:
78a6b8ddocs(auth): define hardened device authorization semanticsaccca05fix(cli): enforce secure server and verification origins27d7b2afix(auth): bound polling and fail closed issuance779353cfix(auth): block redirect leaks and source starvationIndependent review found redirect leakage and global-bucket starvation in the first repair head; both were fixed in
779353c. Focused independent follow-up reported no remaining actionable findings. The real TCP flood test covers valid-unknown and malformed poll bodies with spoofed forwarding headers and bounded durable audit growth. Deterministic issuance tests cover cancellation, rollback/audit, audit-store loss, panic, abort, and completion after the reserved deadline. The cli-mcp branch was inspected read-only; exact semantic integration steps are recorded in artifacts/cli-auth/merge-handoff.md and integration is not claimed complete here.Focused results: login tests 8 passed; device-auth unit tests 7 passed; auth HTTP tests 12 passed; storage tests 14 passed; focused all-target clippy passed with -D warnings.
scripts/verify fast (verbatim final output):
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: UI package svelte-check
PASS fast: Vitest
PASS fast: UI package Vitest
PASS fast: OpenAPI and TypeScript client drift
No push, merge, issue close, integration, or browser gate was performed.
Independent final security review — step 15 / issue #11
Candidate:
779353cb1d7785f10ba51ef112eadd35e05f4178Base:
bfa8af7Branch/worktree:
cli-auth//mnt/hdd/infraternal/.worktrees/cli-authDisposition: not ready to merge
I reviewed the complete
bfa8af7..779353cb1d7785f10ba51ef112eadd35e05f4178diff independently after readingCLAUDE.md,DECISIONS.md,docs/DESIGN.md,docs/PLAN.md, anddocs/contracts.md. I used the candidate-currentcode-review-graphfirst (status,detect-changes,impact,architecture, and focused searches), then inspected the source, tests, and the documentedcli-mcpmerge boundary. Repository files were not edited.Findings
1. High — a bearer-bearing poll reaches SQLite before poll admission
The outer failed-response audit middleware eagerly extracts any
Authorization: Bearer ...value and callsauthenticate_accessbefore it calls the inner route (crates/server/src/lib.rs:363-373).authenticate_accessperforms a token-table query for any syntactically split credential (crates/server/src/storage/mod.rs:1003-1010). The device-token admission gate is only inside the route extractor, after those outer layers (crates/server/src/device_auth.rs:83-107). Consequently an unauthenticated attacker can add a syntactically valid bogus bearer such asx.yto every/api/v1/auth/device/tokenrequest and force one SQLite lookup per request before the bounded source/global buckets run.This leaves the main prior high-severity denial-of-service finding only partially resolved: parsing, device state, and durable poll audits are gated, but database work is not. The flood test sends no
Authorizationheader (crates/server/tests/auth_http.rs:750-765), so it cannot detect this bypass and its durable-audit assertion still passes.Put device-token admission outside all middleware that can authenticate or otherwise touch storage, or explicitly bypass the eager actor lookup for this endpoint. Add a test with a syntactically valid bogus bearer and an instrumented/blocked token query proving rate-limited requests perform no database operation.
2. Medium — expiry can lose to a request that acquires the state lock after the deadline
pollcapturesnowbefore waiting for the device-state mutex (crates/server/src/device_auth.rs:652-666), then uses that stale instant for the deadline decision (crates/server/src/device_auth.rs:699) and can installApproved -> Issuinglater (crates/server/src/device_auth.rs:837-864). The mutex is held across SQLite audit awaits in other paths, including user-code actions (crates/server/src/device_auth.rs:529-608) and admitted polls (crates/server/src/device_auth.rs:776-787). A poll received just before expiry can therefore wait behind storage work, acquire the lock after expiry, pass the stale check, and reserve issuance. Review/approve/deny has the same pre-lock timestamp pattern (crates/server/src/device_auth.rs:528-565), so a decision can also succeed after the deadline.This violates the repaired contract's chosen boundary: expiry must be checked while holding the state lock, and only a reservation actually made before the monotonic deadline may finish later (
docs/contracts.md:198-218). The clock-controlled test starts with an already-Issuingrecord (crates/server/src/device_auth.rs:1548-1568); the HTTP expiry test sleeps before submitting an uncontended request (crates/server/tests/auth_http.rs:650-673). Neither crosses the deadline while waiting to acquire the lock.Take
Instant::now()after acquiring the device-state lock for every deadline-sensitive transition. Add deterministic tests that hold the lock, start poll and approve requests before the deadline, advance past it, then release the lock and require expiry/invalid results with no issuance.3. High integration boundary — the handoff provisions MCP with a human token, bypassing the server's AI-agent plan guardrail
The handoff requires a separate MCP device login and then MCP tool use (
/mnt/hdd/infraternal/artifacts/cli-auth/merge-handoff.md:83-91). Device authorization always issuesTokenKind::Human(docs/contracts.md:185-192;crates/server/src/storage/mod.rs:1166-1175). The server requires a destructive plan only whenactor.kind == TokenKind::AiAgent(crates/server/src/resources.rs:765-816). On the parallel MCP head, destructive tool input defaults todry_run = falseand no plan (cli-mcp@eb149050635b091b836ec0328053a4a8fc4860ff:crates/infraternal/src/mcp.rs:47-64), and the tool forwards those values unchanged (.../mcp.rs:208-226).Thus the handoff's normal MCP login path gives an AI agent a human-kind credential with which a direct destructive tool call is not plan-gated by the server. This contradicts the S1 requirement that MCP use a scoped AI-agent token and destructive tools require dry-run first (
docs/PLAN.md:75-78). The handoff acknowledges that an AI-agent credential path is unresolved but relegates it to a product decision and server-only test evidence (merge-handoff.md:92-97); that is not a safe completion contract for the merge.The integration must resolve secure non-printing provisioning/storage of a scoped
ai_agentrefresh family for MCP, or change the server/tool contract so MCP-origin human device sessions cannot bypass the plan requirement. The merged compiled MCP test must attempt a destructive call without a plan and observeplan_required.4. Medium integration boundary — the handoff retains a redirect-following resource client carrying bearer credentials
The current branch correctly builds its authentication client with
Policy::none()(crates/infraternal/src/login.rs:409-417), and every current device, refresh, and revoke request uses that client. The parallel resource/MCP client, however, builds a default reqwest client without a redirect policy (cli-mcp@eb149050635b091b836ec0328053a4a8fc4860ff:crates/infraternal/src/http.rs:155-167) and attaches a bearer credential before sending every resource request (.../http.rs:279-288). The handoff says to compose that API client with the new auth source “plus its reqwest client” (merge-handoff.md:55-60) but never requires the resource client's redirects to be disabled or adds a redirect test.That omits the requested rule for every credential-bearing request and can reintroduce redirect-driven credential forwarding/downgrade behavior at the merge boundary. Require one shared redirect-disabled client policy for device, refresh, revoke, and bearer-authenticated resource requests, and test both GET and POST redirects with the destination observing no bearer or body.
5. Low — a successful keychain save silently leaves a stale fallback refresh credential when cleanup fails
After a successful keychain write,
CredentialStore::savediscards any error from deleting the file fallback and reports success (crates/infraternal/src/login.rs:83-93). Later, any keychain access error causesloadto fall back to that file (crates/infraternal/src/login.rs:70-79). If the retained file contains the pre-rotation refresh credential, a later keychain outage can replay it and trigger refresh-family revocation; independently, a secret the caller believes migrated to the keychain remains on disk. The tests exerciseFileCredentialStoredirectly but do not inject keychain success plus fallback cleanup failure (crates/infraternal/src/login.rs:572-621).Do not report a successful save while stale fallback cleanup is unresolved. At minimum surface the cleanup failure and prevent later fallback to a known-stale record; cover keychain/fallback transitions with an injectable credential backend.
Prior finding verification
crates/server/src/device_auth.rs:61-75,crates/server/src/device_auth.rs:196-243,crates/server/src/device_auth.rs:920-955). Source rejection precedes global debit, so repeated rejected traffic from one tracked source does not drain the global bucket. Finding 1 is the remaining pre-admission database bypass.crates/infraternal/src/login.rs:463-523). Current authentication requests all use the redirect-disabled client. Finding 4 applies to the documented cross-branch resource-client handoff.Consumedbefore response delivery; task loss and failure-audit loss become terminalFailed; definite errors restoreApprovedonly after durable failure audit (crates/server/src/device_auth.rs:958-1027). The focused tests cover these branches. I found no double-issuance path in the implemented SQLite flow.packages/web/src/routes/device/+page.svelte:22-81). No actionable binding flaw found.Debug/Display; fallback files enforce owned single-link regular files without group/other permissions; redirect and origin handling is safe on this branch. Finding 5 remains, and client-specific CLI/MCP storage remains an explicit merge-boundary requirement.Checks run
Full gates were intentionally not run. The tracked worktree remained at the immutable candidate. Pre-existing untracked
.codex-final-review2-prompt.mdand.codex-final-verify2-prompt.mdwere left untouched.Residual risks
Starting independent security-review fixes.
Branch:
cli-authReviewed immutable head:
779353cb1d7785f10ba51ef112eadd35e05f4178Base:
bfa8af7Worktree:
/mnt/hdd/infraternal/.worktrees/cli-authScope: pre-admission device-token routing, lock-acquisition time refresh and deterministic contention tests, credential-backend cleanup safety, and integration handoff hard blockers. The
.codex-*prompt files remain orchestrator-owned and will not be edited or committed.Final verification evidence: CLI authentication
cli-auth779353cb1d7785f10ba51ef112eadd35e05f4178779353cb1d7785f10ba51ef112eadd35e05f4178scripts/verify browserprocess (PID 1261519) to exit before starting this browser gate.Gate results
scripts/verify fast0/mnt/hdd/infraternal/artifacts/cli-auth/gates/fast-final.logscripts/verify integration1/mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final.logscripts/verify browser0INFRATERNAL_ARTIFACT_DIR=/mnt/hdd/infraternal/artifacts/cli-auth;INFRATERNAL_BROWSER_DEVICE_ONLYunset/mnt/hdd/infraternal/artifacts/cli-auth/gates/browser-final.logRequired screenshots
/mnt/hdd/infraternal/artifacts/cli-auth/device-light-1440x900.png— 1440 x 900, PNG RGB/mnt/hdd/infraternal/artifacts/cli-auth/device-dark-1440x900.png— 1440 x 900, PNG RGB/mnt/hdd/infraternal/artifacts/cli-auth/device-light-390x844.png— 390 x 844, PNG RGB/mnt/hdd/infraternal/artifacts/cli-auth/device-dark-390x844.png— 390 x 844, PNG RGBWarnings
scripts/verify integrationexited1.docker_and_podman_share_the_real_engine_contracttimed out atcrates/engine/tests/integration.rs:761:6while waiting for an event stream. During cleanup, three Podman containers did not stop on SIGTERM within 10 seconds and Podman resorted to SIGKILL.Security fix2 writing job started. Branch: cli-auth. Base:
bfa8af7. Reviewed head:779353cb1d. Scope: pre-middleware device-poll admission, monotonic lock-acquisition deadlines, credential fallback cleanup safety, and integration handoff blockers. The orchestrator-owned untracked .codex-* files will remain untouched; this job will not push or close the issue.Security fix2 writing job finished.
Branch: cli-auth
Base:
bfa8af7Reviewed input head:
779353cb1dFinal head:
4ccb94f1b5Commits:
9677172c2bdocs: tighten auth hardening contract0bd2b48b3afix(server): enforce pre-storage poll admission4ccb94f1b5fix(cli): fail closed on stale fallback credentialsFocused checks (verbatim summaries):
cargo test -p infraternal-server device_auth::tests -- --nocapture
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 7 filtered out
cargo test -p infraternal-server device_poll_admission_precedes_failed_response_actor_authentication -- --nocapture
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 15 filtered out
cargo test -p infraternal-server device_poll_http_flood_is_admission_limited_before_durable_audit -- --nocapture
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 11 filtered out
cargo test -p infraternal login::tests -- --nocapture
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
cargo fmt --all -- --check
PASS (no output)
cargo clippy -p infraternal-server -p infraternal --all-targets -- -D warnings
Finished dev profile [unoptimized + debuginfo] target(s) in 0.31s
scripts/verify fast
PASS fast: JavaScript dependencies
PASS fast: build web app
PASS fast: cargo fmt --check
PASS fast: cargo clippy
PASS fast: cargo test
PASS fast: svelte-check
PASS fast: UI package svelte-check
PASS fast: Vitest
PASS fast: UI package Vitest
PASS fast: OpenAPI and TypeScript client drift
/usr/bin/git diff --check
PASS (no output)
Artifacts updated: /mnt/hdd/infraternal/artifacts/cli-auth/findings.md, merge-handoff.md, and final-fix2-result.md. Cross-branch hard blockers remain explicit: device flow is human identity only; merged MCP AI-agent operations need the product-approved stored ai_agent credential path and compiled plan_required proof; every merged credential-bearing API/resource client needs normalized origins and the shared redirect-disabled constructor with GET/POST redirect-destination tests. No cli-mcp file was edited or claimed implemented. The .codex-* prompt files remain untouched. Not pushed; issue remains open.
fix-cli-mcp started on branch cli-mcp at base merge head
1c550b5(current devbadd796merged). Scope: fix F1/F2/F3/F7/F8 and cheap F4/F5/F6 in crates/infraternal, tests, benchmark; no push.Independent final security re-review complete for candidate
4ccb94f1b5fff16361e77ba5cb3efd9756c90fa6against basebfa8af7.Disposition: not ready to merge.
Actionable current-branch findings:
Approved -> Issuingvalidate expiry before awaiting the durable audit, then transition afterward using the stale timestamp. SQLite contention can therefore install either transition after the deadline. Initiation similarly derives expiry and first-poll timing from a pre-audit instant. The new state-lock contention tests do not exercise audit contention, and the poll contention test fails reproducibly on this candidate (Internalinstead ofExpiredToken).expired_tokeninstead of retaining their terminal semantics.Verified fixed/currently sound: poll admission before body parsing and bogus-bearer middleware storage; bounded poll cardinality/audit growth and source-before-global ordering; secure origins and redirect-disabled auth requests; stale fallback cleanup/restart fail-closed behavior; detached issuance cancellation/panic/abort/failure-audit handling; post-reservation completion; and anti-phishing reviewed-code UI binding.
Residual integration blockers remain explicit: MCP needs a real stored scoped
ai_agentfamily and all merged credential-bearing resource clients must be redirect-disabled. The handoff still conflates MCP human device-login and AI-agent credentials into the same origin+infraternal_mcpstore slot, with no trustworthy runtime token-kind check; that must be corrected during integration.Focused checks: diff check passed; bogus-bearer admission, poll flood, device authorization, expiry/restart, failure-audit, CLI credential, and OpenAPI drift checks passed;
device_auth::testsfailed 1 of 9 because the new poll contention test returnedInternalrather thanExpiredToken. Full gates were not run. Issue #11 remains open.Full report:
/mnt/hdd/infraternal/artifacts/cli-auth/security-review-final3.mdFinal security fix pass started. Branch: cli-auth. Reviewed/base heads:
4ccb94f1b5/bfa8af7. Scope: final3 actionable device-auth reservation/deadline/cancellation findings, deterministic barriers/tests, terminal-state classification, and merge-handoff credential separation blocker. Worktree has pre-existing untracked .codex-final-fix3-prompt.md; it will remain untouched and uncommitted.Security review final4 started. Branch: cli-auth. Exact review head:
e23a10d6f0. Base:bfa8af7. Scope: complete bfa8af7..HEAD diff; no tracked-file edits.Security review final4 finding (Medium): the single device-state mutex is still held across durable SQLite audit I/O. Initiation acquires it at crates/server/src/device_auth.rs:439, awaits audit at :489-500, and keeps it through record insertion at :520-540. Pending/slow-down polling acquires it at :927, mutates next_poll/interval at :1021-1022 or :1054, then awaits audit at :1024-1035 / :1056-1067 without releasing it. Impact: admitted unauthenticated traffic or SQLite contention serializes every initiation/review/decision/poll behind storage and can consume legitimate 600s deadlines; pending/slow-down audit failure also leaves the poll schedule mutation applied despite the contract audit-before-action rule. The initiation barrier test at :1923-1957 proves post-audit timestamp sampling but does not assert unrelated state operations proceed while audit is blocked. Remediation: reserve only bounded per-record state under the lock, release it before audit, and have a detached supervisor complete/restore the exact reservation; add deterministic concurrency and audit-failure rollback tests.
Security review final4 finished.
Verdict: NOT READY TO MERGE
Exact head:
e23a10d6f0Base:
bfa8af7ffcFinding: 1 Medium. Initiation and pending/slow-down polling still await durable SQLite audit while holding the global device-state mutex; poll schedule mutations also survive definite audit failure. Exact evidence and remediation are in the full artifact. The claimed DecisionAuditing/IssuanceAuditing deadline, cancellation, restoration/fail-closed, single-issuance, and terminal-error fixes otherwise verified.
Commands and exact result output:
cargo test -p infraternal-server device_auth::tests -- --nocapturetest result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 7 filtered out; finished in 0.35scargo test -p infraternal-server device_authorization_enforces_binding_decisions_single_issue_and_refresh_family_reuse -- --nocapturetest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 11 filtered out; finished in 15.12scargo test -p infraternal-server device_authorization_expiry_restart_guess_limits_and_audit_failure_are_safe -- --nocapturetest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 11 filtered out; finished in 0.32scargo test -p infraternal-server device_poll_admission_precedes_failed_response_actor_authentication -- --nocapturetest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 22 filtered out; finished in 0.06scargo test -p infraternal-server device_poll_http_flood_is_admission_limited_before_durable_audit -- --nocapturetest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 11 filtered out; finished in 0.36scargo test -p infraternal-server device_issue_failure_audit_is_independent_and_reports_store_failure -- --nocapturetest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 13 filtered out; finished in 0.03scargo test -p infraternal login::tests -- --nocapturetest result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.01scargo fmt --all --checkPASS (no output)cargo clippy -p infraternal-server --all-targets -- -D warningsChecking infraternal-server v0.0.1 (/mnt/hdd/infraternal/.worktrees/cli-auth/crates/server)Finished dev profile [unoptimized + debuginfo] target(s) in 3.02s✨ openapi-typescript 7.13.0🚀 /tmp/tmp.kFkvxWY7xa/generated-openapi.json → /tmp/tmp.kFkvxWY7xa/generated-schema.ts [166.1ms]PASS (all cmp commands produced no output)/usr/bin/git diff --check bfa8af7..HEADPASS (no output)Full fast/integration/browser gates intentionally not run.
Artifacts:
/mnt/hdd/infraternal/artifacts/cli-auth/security-review-final4.md/mnt/hdd/infraternal/artifacts/cli-auth/review-result4.mdNo tracked files edited; no commit, push, or issue close.
Final security fix 4 started on branch
cli-auth, basebfa8af7, starting heade23a10d6f04de0942aaa65fcadb275032473f538. Scope: remove all durable audit/storage I/O from the global device-state mutex in initiation and pending/slow-down polling, add bounded reservation/supervisor semantics with deterministic cancellation/failure/concurrency coverage, then run the requested gates. No push, rebase, merge, or issue closure.Contract-first commit:
872b968c991ac665b752c24480f566a4229863ae(docs: specify isolated device audit reservations).docs/contracts.mdv3.9 now requires: no global/per-authorization device-state mutex across durable audit/storage I/O; bounded uniquely identified initiation and pending/slow-down poll reservations; detached supervision across caller cancellation; exact removal/restoration on definite pre-action audit failure; fail-closed indeterminate outcomes; post-audit initiation lifetime/first-poll timing; pre-expiry reserved completion; and concurrent poll re-evaluation that cannot bypass interval/slow_down or issue twice.git diff --checkpassed with no output before commit.Fix round progress on
cli-mcp:plan_required, foreign-tokenforbidden, changed-parameterplan_stale,plan_expired,plan_used, andplan_indeterminate, plus deterministic mutation replay.--jsonerrors use the contract envelope andps ewwdoes not expose the MCP bearer credential. The environment fallback re-execs MCP through a 0600 handoff file because Linux/proc/<pid>/environretains the initial environment afterunsetenv.forbidden, indeterminate plans returnplan_indeterminate, andplan_staleresponses carry typed details instead of panicking.Final fix4 finding resolved at
6ec8e857b84bb4b5b92e2e0c15930f377d6b72bd. Evidence: initiation and pending/slow-down polls now install bounded uniquely identified internal reservations under the device-state mutex, release it before every audit/storage await, and complete through detached supervisors. Initiation exposes codes and starts its 600-second lifetime/first poll only after durable accepted audit; cancelled delivery removes the never-exposed record. Poll schedules remain unchanged until durable rejected audit; definite failure restores the exact prior state/interval/next_poll, and indeterminate outcomes become terminalFailed. Same-authorization concurrent polls wait outside the mutex and re-evaluate the committed schedule. Deterministic unit coverage increased from 16 to 21 tests and proves unrelated operations during blocked audits, exact failure restoration/removal, cancellation, indeterminate fail-closed behavior, slow_down serialization, and single issuance.Final security fix4 finished. Head:
6ec8e857b84bb4b5b92e2e0c15930f377d6b72bd.Commits:
872b968c991ac665b752c24480f566a4229863aedocs: specify isolated device audit reservations6ec8e857b84bb4b5b92e2e0c15930f377d6b72bdfix: isolate device authorization auditsExact focused results:
Changed tracked files in this fix:
docs/contracts.md,crates/server/src/device_auth.rs. Durable evidence updated in/mnt/hdd/infraternal/artifacts/cli-auth/findings.md; concise report written to/mnt/hdd/infraternal/artifacts/cli-auth/final-fix4-result.md. No push, rebase, merge, generated-artifact edit, integration/browser run, or issue closure. The three pre-existing untracked prompt files remain untouched.Final independent security review 5 started. Branch: cli-auth. Exact review head:
6ec8e857b8. Base:bfa8af7ffc. Scope: complete bfa8af7..HEAD security surface, with special attention to872b968and6ec8e857; focused tests/static checks only; no tracked-file edits.Final independent security review 5 finished at exact head
6ec8e857b8(basebfa8af7ffc). Verdict: NOT READY TO MERGE. Finding: Medium — the implementation-level lock/audit-order issue is corrected on inspection, but the security-critical proof is not deterministic. In crates/server/src/device_auth.rs:2840-2856 the concurrent-poll test uses one yield_now and only observes the first PollAuditing state; it never proves the second poll reached completion.changed().await at 1064-1068. The new InitiationAuditing/PollAuditing live-count, non-eviction, and user-index cleanup branches at 1676-1711 also lack direct invariant tests. Checks: device_auth tests 21 passed; auth_http 12 passed; storage 14 passed; CLI login/store 10 passed; six focused device/refresh tests passed; clippy for server+CLI all targets with -D warnings passed; fmt passed; OpenAPI/schema/client drift passed; git diff --check bfa8af7..HEAD passed. Full fast/integration/browser gates intentionally not run. Cross-branch MCP credential-kind/slot/resource-client blockers remain separate. Artifacts: /mnt/hdd/infraternal/artifacts/cli-auth/security-review-final5.md and /mnt/hdd/infraternal/artifacts/cli-auth/review-result5.md. No tracked files edited; no commit, push, or close.Security test-fix started. Branch cli-auth; base
bfa8af7ffc; starting head6ec8e857b8. Scope: deterministic test-only proof for poll watch-wait and direct InitiationAuditing/PollAuditing admission, eviction, cleanup, cancellation, and tombstone invariants from final security review 5. Production semantics remain unchanged unless a deterministic test exposes a defect.fix-cli-mcp finished at
c157d700903f4a91cd86a8d991ebce9179eda9f6(branchcli-mcp; no push).Per-finding status:
--idempotency-keyremains an explicit override. The HTTP test proves identical retries send the same key and the live server returns the replayed result.INFRATERNAL_TOKEN/INFRATERNAL_TOKEN_FILEis captured once and removed before the MCP child runs. Linux MCP startup re-execs through a 0600 handoff file because/proc/<pid>/environretains the initial environment snapshot. The live MCP test checksps ewwand finds no token. File/keychain storage is preferred; environment is documented as the CI fallback.CredentialProvider::access_tokenis async and can refresh; the test covers an expired access token plus a valid refresh token. The seam is compatible with the parallel keychain store work.plan_required), reused plan (plan_used), another token (forbidden), changed parameters (plan_stale), expired plan (plan_expired), and indeterminate outcome (plan_indeterminate).psbenchmark. It seeds 500 controlled created/stopped containers (250 Docker, 250 rootless Podman), then measures the compiled CLI and reqwest HTTP client over real TCP. VM smoke numbers (not reportable perf-test-VM numbers): HTTP/TCP p50 4236us, p99 5540us; compiled CLI/TCP p50 18ms, p99 21ms. Criterion: HTTP/TCP[3.8997 ms 3.9662 ms 3.9829 ms]; compiled CLI/TCP[19.697 ms 19.813 ms 19.842 ms].plan_stale,plan_expired,plan_used, andplan_indeterminate; indeterminate explicitly requires re-inspection before a new plan.--jsonerrors: one{code,message,request_id,retryable,details}object on stderr and empty stdout on failure.outputSchemaand read-only/destructive/idempotent/open-world annotations to every MCP tool, with metadata coverage tests.Refusal examples:
ai_agenttoken:infraternal --server "$SERVER" --json stop "$TARGET"(no plan) returnscode: "plan_required", with structured JSON on stderr and no stdout.tools/call stop_containerwithtargetand noplan_idreturnscode: "plan_required".Final gates:
scripts/verify integrationreached and passed HTTP, Docker socket, and rootless Podman socket checks, then stopped at the existing engine contract failure:The scoped real CLI/MCP integration was run separately after that gate and passed:
Correction to the integration excerpt above: the exact
scripts/verify integrationfailure output was:Final security test-fix result — issue #11
Branch:
cli-authBase:
bfa8af7ffcc146d0d87a14e8bdcf5ed8434eefa4Starting head:
6ec8e857b84bb4b5b92e2e0c15930f377d6b72bdFinal commit/head:
564b75962bc7c0a4d5fe81eeb7ae4646aed2daeaResult
The final security-review finding is resolved with deterministic test proof.
No production defect was uncovered, and production device-authorization
semantics were not redesigned.
Changed tracked files:
crates/server/src/device_auth.rscrates/server/src/storage/mod.rsThe storage change is a
cfg(test)-only audit-count query used to prove thatconcurrent polling creates exactly one accepted issuance. All synchronization
added by this commit is also
cfg(test)-only.The five pre-existing untracked
.codex-*-prompt.mdfiles were not touched.Deterministic proof added
and releases the mutex, immediately before
completion.changed().await.The test reaches that checkpoint before the first audit is released, then
proves the waiter wakes and returns the committed
slow_downschedule.InitiationAuditingandPollAuditing: both are live for global and per-source admission, andneither is evictable by
make_roomwhile supervisor-owned even after itsdeadline.
schedule restoration, and live counts. Channels and one-shot checkpoints
replace the prior cancellation polling loop.
Failedtombstones; at full capacity each is evicted and its exactuser_indexdigest is removed.auth.device.issueaudit.Exact verification output
Graph review output:
Commit output
Starting final independent security review for step 15a
cli-auth.cli-authbfa8af7ffcc146d0d87a14e8bdcf5ed8434eefa4564b75962bc7c0a4d5fe81eeb7ae4646aed2daeaNo tracked files will be changed; the review artifacts will be written outside the repository worktree.
Final independent security review completed at pinned HEAD
564b75962bc7c0a4d5fe81eeb7ae4646aed2daeaagainst basebfa8af7ffcc146d0d87a14e8bdcf5ed8434eefa4.Result: No findings.
Commit
564b759deterministically resolves the sole prior finding: the concurrent poll test observes the waiter immediately beforecompletion.changed().awaitand withholds audit release until then; direct tests now proveInitiationAuditing/PollAuditinglive counts and non-eviction, exact restoration/removal,user_indexcleanup, indeterminate tombstone eviction, and exactly one acceptedauth.device.issue. All new instrumentation is#[cfg(test)]; production semantics are unchanged.Evidence:
Full review:
/mnt/hdd/infraternal/artifacts/cli-auth/security-review-final6.mdConcise report:
/mnt/hdd/infraternal/artifacts/cli-auth/review-result6.mdResidual test scope: full integration/browser gates were not run in this review. Cross-branch requirements already recorded in
merge-handoff.mdwere not reported as isolated-branch defects.Final verification worker started for step 15a cli-auth.
cli-auth564b75962bc7c0a4d5fe81eeb7ae4646aed2daea.codex-*-prompt.mdonlyscripts/verify fast,scripts/verify integration, fullscripts/verify browser/mnt/hdd/infraternal/artifacts/cli-auth/gates/No tracked files will be edited and this job will not commit, push, merge, rebase, or close the issue.
Orchestrator
3e052fnow owns this step (handover on #16). Plan: letcli-authfinish, then an independent review of 15a ongpt-6-sol/medium(it is an auth surface). Then mergecli-mcpontocli-auth, one combined fix round if the review needs one, and one verify on the combined head once the engine event-stream fix from #7 lands. Then merge todev.Final verification worker finished for step 15a cli-auth at pinned HEAD
564b75962bc7c0a4d5fe81eeb7ae4646aed2daea.Gate evidence:
scripts/verify fast— exit0PASS fast: JavaScript dependenciesPASS fast: build web appPASS fast: cargo fmt --checkPASS fast: cargo clippyPASS fast: cargo testPASS fast: svelte-checkPASS fast: UI package svelte-checkPASS fast: VitestPASS fast: UI package VitestPASS fast: OpenAPI and TypeScript client driftscripts/verify integration— exit1engine adapters against Docker and Podmanindocker_and_podman_share_the_real_engine_contractatcrates/engine/tests/integration.rs:776because no record matchedstdout-linewith non-truncated stdout.FAILED. 6 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 208.63s.INFRATERNAL_ARTIFACT_DIR=/mnt/hdd/infraternal/artifacts/cli-auth env -u INFRATERNAL_DEVICE_ONLY scripts/verify browser— exit0Approval screenshots regenerated and verified:
device-light-1440x900.png: 1440×900, 91,797 bytes, mtime2026-09-23 00:30:42.168985026 +0200device-dark-1440x900.png: 1440×900, 91,786 bytes, mtime2026-09-23 00:31:23.184507249 +0200device-light-390x844.png: 390×844, 46,299 bytes, mtime2026-09-23 00:31:14.308610634 +0200device-dark-390x844.png: 390×844, 46,657 bytes, mtime2026-09-23 00:31:44.400260146 +0200Final repository state:
564b75962bc7c0a4d5fe81eeb7ae4646aed2daea..codex-*-prompt.mdfiles are present.Full commands, verbatim combined gate output, screenshot metadata, and retry decision are recorded at
/mnt/hdd/infraternal/artifacts/cli-auth/verification-final3.md. Final verification is not green due to the integration failure. This is the worker finish evidence, not the overall final job comment.Final integration retry verification on cli-auth at
564b75962b.The original valid integration attempt exited 1 at the engine contract assertion and is preserved at /mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final-attempt1.log. A later workspace-write sandbox invocation stopped at Node listen EPERM on 127.0.0.1 before product behavior; it is excluded from product gate attempts and preserved at /mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final-invalid-sandbox.log.
After checking for unrelated cargo, verify, Playwright, Docker, Podman, and engine test activity, I ran exactly one valid full retry:
scripts/verify integration > /mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final.log 2>&1Exit status: 1. It reached the real engine adapter tests and repeated the same failure. Relevant verbatim output:
The complete verbatim output for all three invocations and the gate outcome are recorded in /mnt/hdd/infraternal/artifacts/cli-auth/verification-final3.md. HEAD remains
564b75962b; tracked status is clean, with only the same seven orchestrator-owned prompt files untracked. No further retry was run.Step 15a
cli-authfinal handoff at564b75962bc7c0a4d5fe81eeb7ae4646aed2daea(branchcli-auth, basebfa8af7ffcc146d0d87a14e8bdcf5ed8434eefa4). No push, merge, rebase, or issue closure was performed.Implemented RFC 8628 device authorization contract/types/server state machine, passkey-protected approval/denial page, audited and bounded polling/guess controls, human access/refresh issuance and revocation, CLI login/logout, keychain plus permission-checked 0600 fallback storage, refresh helper, generated OpenAPI/client artifacts, real HTTP coverage, and full browser coverage. Pending authorizations are process-local and invalidated on restart. Secrets are stored and compared through public lookup id plus SHA-256 verifier and are never logged.
Independent Sol security review 6 at this exact head: No findings. Review:
/mnt/hdd/infraternal/artifacts/cli-auth/security-review-final6.md.Final gate evidence:
scripts/verify integrationis not green for an unchanged, out-of-scope engine contract failure. Two valid unrestricted runs passed dependency setup, web/release builds, real HTTP behavior, Docker ping, and rootless Podman ping, then failed identically:The cli-auth diff does not modify
crates/engine. Attempt 1:/mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final-attempt1.log; valid retry:/mnt/hdd/infraternal/artifacts/cli-auth/gates/integration-final.log. An invalid restricted-sandbox invocation that failed to bind localhost withEPERMbefore product behavior is separately preserved and excluded:integration-final-invalid-sandbox.log. Complete verification report:/mnt/hdd/infraternal/artifacts/cli-auth/verification-final3.md.Approval screenshots were regenerated by the full browser gate and visually inspected with no overlap, clipping, or unreadable contrast:
/mnt/hdd/infraternal/artifacts/cli-auth/device-light-1440x900.png/mnt/hdd/infraternal/artifacts/cli-auth/device-dark-1440x900.png/mnt/hdd/infraternal/artifacts/cli-auth/device-light-390x844.png/mnt/hdd/infraternal/artifacts/cli-auth/device-dark-390x844.pngUI skill note: the prompt said the design skills were unavailable, but they were installed when the UI worker ran; it loaded and applied
emil-design-engand all requestedbetter-*skills. The final browser result reflects those rules.Open integration requirements are recorded in
/mnt/hdd/infraternal/artifacts/cli-auth/merge-handoff.md. In brief: the parallel CLI/MCP merge must key credentials by normalized origin + public client + purpose/slot; human device credentials cannot serve as MCP operational AI-agent credentials; MCP must validate kind from trustworthy server-side state; refresh must be singleflight; and every credential-bearing resource/MCP client must use the normalized redirect-disabled HTTP path. These are cross-branch merge blockers, not defects in this isolated branch.Final tracked worktree status is clean. The remaining merge blocker for this isolated job is the repeated pre-existing engine integration failure above.
Orchestrator: 15a is done at
564b759, but its own "independent" security review was spawned by the author job, so it does not count as the step's independent review. A fresh Sol review runs on the integrated head instead.Decision, pending owner confirmation (flagged for the morning). MCP holds a scoped
ai_agentcredential minted through the existingPOST /api/v1/tokens(§1.1, §2.1–2.3):infraternal mcp setup --scope …, which mints a token narrowed to the requested grants, never wider than the caller's.infraternal mcprefuses to run on ahumancredential, so mandatory plans (§2.4) always apply.INFRATERNAL_TOKENenvironment credential is removed.This comes from the 15a author's merge handoff, which calls both items hard merge blockers.
cli-integrate(branchcli, fromcli-auth): a semantic merge ofcli-mcpc157d70, plus the credential design above →gpt-6-sol/medium(credentials and tokens, and a multi-branch merge). Then an independent review, one fix round, a verify, and the merge todev. Thecli-mcpworktree is removed; the branch is kept.Starting cli-integrate on branch cli at base
564b75962b. I am integrating functional CLI/MCP commits throughc157d70, then current dev, with credential isolation and real compiled verification. No push.Integration finding: device authorization returns human tokens regardless of registered public client id. The MCP runtime now selects a separate ai-agent-mcp credential slot and will require the server-returned token kind on refresh. MCP setup uses a human CLI credential to mint an ai_agent token through POST /api/v1/tokens; the server's validate_token_mint enforces grant narrowing. This credential design is pending owner confirmation as requested.
Finished cli-integrate on branch cli at
4a20c0bbdb(no push). Merged cli-mcp throughc157d70(functionalddf2b93,eb14905; fixes2317da0,9867cf1,c157d70) with cli-auth base564b759, then merged origin/dev6270e0f. The semantic merge retained CLI resource commands, MCP, the device login/logout flow, exit codes, and the connect-info server listener. Cargo.lock and generated OpenAPI/TypeScript schema were regenerated.Credential design, pending owner confirmation: environment bearer support is removed. Human resource CLI commands use a device-authorized human credential in the origin/client/purpose-bound human-cli slot.
mcp setup --scope ... [--host-tag ...] [--name ...]mints a narrowed ai_agent family through POST /api/v1/tokens and stores its refresh credential in the separate ai-agent-mcp slot; MCP refreshes at startup and checks server-returned kind, andmcp revokerevokes that family. Keychain and 0600 fallback records validate origin, client, and purpose. A short-lived human access token is retained until near expiry so a later CLI process can execute a plan bound to that token id. Per-process refresh is serialized. The compiled real-engine test covers device login, CLI resources/plans, grant narrowing, MCP no-plan refusal and planned execution, cross-token plan refusal, two slots, revocation, kind rejection, refresh rotation and family reuse, and process credential visibility. The 500-container compiled CLI benchmark remains in crates/infraternal/benches/containers.rs and now uses a stored credential.Final-head gate output, quoted verbatim:
This is the known dev Podman event-stream failure; no crates/engine files were changed. Since the integration gate stops there, I ran its remaining stages directly at the same head. Their test-result lines, quoted verbatim:
Those are respectively resources_http (real Docker/Podman), cli_mcp (compiled CLI/MCP and real engines), and real_sshd.
Open questions: owner confirmation of the credential design recorded in docs/contracts.md §1.1; integration gate awaits the stream branch's engine event-stream fix. Branch cli is clean and ready for the orchestrator to fetch/review; I did not push or close this issue.
Review started for step 15 on branch
cli, base6270e0f, head4a20c0bbdbbbfe73a23150246885a53ba4eca054. Scope is a read-only defensive audit of6270e0f..4a20c0b; findings and gate output will follow. No source edits or commits.Independent review:
6270e0f..4a20c0b(step 15)Read-only audit of branch
cliat4a20c0bbdbbbfe73a23150246885a53ba4eca054. Findings were formed from contract and diff before issue history. No source edits or commits.BLOCKS MERGE — distinct CLI/MCP mutations reuse an idempotency key.
crates/infraternal/src/http.rs:296-311choosesderive_idempotency_keywhenever the caller omits a key;:403-435hashes only action, address and request body. A secondstop --dry-runon the same target within the server's 24-hour idempotency retention replays the first plan, even if it was used or the target changed. Astart → stop → startsequence can replay the first successful start without dispatching the second.crates/infraternal/tests/cli_mcp.rs:234-313supplies explicit keys on repeat stop planning, leaving the default path uncovered;http.rs:531-557actually asserts equal keys for equal request bodies. Failing behavioral test: against the compiled binary and real server, perform stop dry-run/execute, start, then stop dry-run again with no explicit key; require a new plan id and a successful second execution. Also requirestart → stop → startto leave the container running after the second start.BLOCKS MERGE — failed remote revocation destroys the only retry credential. Both logout and MCP revoke call
finish_logout(crates/infraternal/src/login.rs:561-582,649-670), which executesdelete_local()?before checkingremote(:672-688). When the server is unavailable, the command fails but its stored refresh credential disappears while the server family remains valid; a retry cannot revoke it.login.rs:1285-1312expressly tests the faulty deletion. Failing behavioral test: mint an MCP family, make/auth/revokeunavailable, runmcp revoke, restore the server and retry; require a successful server revocation. Repeat forlogout.BLOCKS MERGE — malformed user-code submissions evade the device guessing path. Review, approve and deny extract
ApiJson<DeviceUserCodeRequest>before enteringuser_code_action(crates/server/src/device_auth.rs:715-773). Invalid JSON or an invalid schema returns directly fromApiJson(crates/server/src/lib.rs:51-78), before the source/user counters and HMAC lookup atdevice_auth.rs:842-866. Even a malformed-format string bypasses the keyed digest.docs/contracts.md§1.1 requires every submission, including syntactically invalid input, through the same normalization, rate-limit and keyed-digest lookup. Failing behavioral test: one authenticated browser bearer submits five{}or{"user_code":4}bodies from one peer, then a valid pending code; the sixth request should returnrate_limited, but the implementation reviews it.BLOCKS MERGE — MCP output schemas omit fields needed for planned actions.
crates/infraternal/src/mcp.rs:92-125publishesplanas an opaque nullable object andengines,portsandmountsas arrays with no item schema. The requiredplan_idis present incrates/proto/src/plans.rs:169-193but absent fromtools/list, forcing an AI agent to guess the result path for the second destructive call. Existing schema tests atmcp.rs:460-480andcli_mcp.rs:338-357only assert schema presence. The MCP tools specification describes output schemas as type information for structured results. Failing behavioral test: calltools/listover compiled stdio and assert the stop/restart/remove output schemas describeplan.properties.plan_id, plus typed host engine, port and mount items.LATER — first poll interval starts before initiation response is committed for sending.
crates/server/src/device_auth.rs:662-678setsnext_poll = now + initial_intervalin the detached supervisor, then delivers the result to the request handler; the handler forms its HTTP response afterward at:609-615. A delayed handoff can make an immediate poll after the authorization response eligible earlier than the five seconds required bydocs/contracts.md§1.1. Failing test: pause between supervisor completion and HTTP response commit for more than five seconds, then poll immediately after receiving the authorization; expectslow_down, notauthorization_pending.LATER — available OS-keychain behavior lacks a real-backend integration test. The compiled integration test logged
Warning: the OS keychain is unavailable; using a permission-restricted credential file.and so covered only the fallback. Available-keychain, migration and tamper tests useFakeKeychain(crates/infraternal/src/login.rs:940-981,1050-1128). This leaves the platform backend and its account binding unproved under the job's no-mocks test requirement. Required test: run login, read, slot isolation, tamper rejection and fallback-to-keychain migration against an available OS keychain, with a disposable account and real server.Checks
cargo test -p infraternal-server --test auth_http -- --nocapture: 12 passed; 0 failed (auth-http-gate.log).cargo build -p infraternal: passed (build-gate.log).cargo test -p infraternal --test cli_mcp -- --ignored --nocapture --test-threads=1: 1 passed; 0 failed against the compiled binary, real server, Docker and rootless Podman (cli-mcp-gate.log).python3 regression_probes.py: 4 failed, as expected (regression-probes.log). These are source-level contract probes outside the source tree; the behavioral test cases above remain to be added to the project test suites.into_make_service_with_connect_info::<SocketAddr>()atcrates/infraternal/src/main.rs:254-259, so device rate limiting gets the transport peer IP. The approval-page source and existing desktop/mobile browser screenshots show the registered client, unverified device name, request time and warning before the decision controls. The existing 390×844 screenshot has no visible horizontal overflow; no additional UI finding.Starting step 15 fix round on branch cli at base
4a20c0bbdb. Fetched devbba250fde3; resolving merge conflicts before the four review fixes. Merge preserves peer ConnectInfo and accepted-socket TCP_NODELAY.Fix-round findings on cli after merging dev
bba250f:_sqlx_migrations.versionUNIQUE constraint. Renumbered stream sessions to 0009; the test now passes.Later only: first poll timer starts before the authorization response is committed; real-backend OS keychain integration remains untested. These are recorded, not changed in this fix round.
Orchestrator:
fix-cliwas cut off by the Codex account usage limit (until 2026-09-28 10:05) after committing its fixes. Branchcliat3dee81bis pushed to Forgejo. The fixes (c8aa48fplus test updates) are not gated or verified. The verify must also check54b57a1, which touchescrates/engine/src/lib.rs, outside this step's scope. The MCP credential design still awaits the owner's confirmation. See #16.Starting verify-cli on branch
cliat3dee81b1a5e76c76f2aa53de2cdc89b830837129; base headdevatbba250fper job handoff. I will fetch currentdevand verify the requested findings and gates before making any product change.verify-cli result on
cli, head6932126da8df9b15cae1f3e2957a3a964f202556(merged current dev244df292over basebba250f). Working tree clean. No product changes or push.1. Four review findings
PASS — idempotency.
python3 /mnt/hdd/infraternal/artifacts/review-cli/regression_probes.py: 4 probes passed. The compiled real-server testcli_and_mcp_use_the_public_http_contractgets a new plan id for a repeated logical stop, executes the later stop/start, and inspects the container running. The integration gate explicitly runs this ignored Docker/Podman test.PASS — failed revocation preserves credentials. The same real-server test stops the server, confirms failed
mcp revokeandlogoutretain their local records, restarts the server and successfully retries both. It also checks--local-onlyremoves the MCP record and emits the remote-family warning.PASS — malformed guesses count.
malformed_user_code_submissions_share_real_peer_guess_limitincrates/server/tests/auth_http.rssends five malformed requests from one real HTTP peer; the sixth valid-code submission is rate limited. This test runs in fast.PASS — MCP schemas. The compiled MCP test checks
plan_id, typed ports, mounts and host engine items intools/list; it validates real structured results against the published schemas, including tool results with a plan.2. Production listener
PASS.
accepted_server_socket_has_tcp_nodelay_and_handler_gets_real_peeruses a real TCP connection to assert accepted sockets have TCP_NODELAY and handlers receive the connected peer address. Production usesinto_make_service_with_connect_info::<SocketAddr>(); the device-auth HTTP tests also prove rate limiting uses the peer despite forged X-Forwarded-For headers.3. Engine helper and integration waits
FAIL — the termination behavior passes, but the contract's one-second bound is not established.
54b57a1detaches the no-output signal helper so cleanup does not wait for an attached output stream held open by stalled PTY traffic; inspecting the helper confirms successful exit. The named real-engine testauthorization_loss_terminates_stalled_stdin_on_both_enginespassed on Docker and rootless Podman:cargo test: 1 passed, 10 filtered out (1 suite, 14.25s)It confirms process exit and the forbidden completion audit on both engines. However, docs/contracts.md §2.3 requires grant-shrink cleanup within one second. The new create/start timeout is four seconds and each
inspect_execrequest has no call-level deadline; the regression allows seven seconds. So the run verifies eventual termination, not the one-second bound. No product change was made because this run did not demonstrate an actual over-one-second termination.The integration-test wait changes have stated reasons:
4a1bc01attributes health/log visibility waits to asynchronous engine health and log flushing;3dee81battributes fixture health settings to Podman load.PASS — credential design. No
INFRATERAL_TOKENreference is present.mcp setupstores a narrowedai_agentfamily; a wider grant request is refused with 403. MCP checks the server-issued kind and refuses a human family. Credential records and keychain accounts bind normalized origin, client id and purpose. A compiled MCP process using isolated credentials had no access/refresh token in/proc/<pid>/cmdline,/proc/<pid>/environ, stdout or stderr. A trace-level local-server run logged rejected auth requests without either marker credential; the--jsonerror contained only the generic API error. Runtime logging probes used disposable rejected credentials; source paths were also checked.Gates on this head
scripts/verify fast:scripts/verify integration:scripts/verify browser:Orchestrator: step 15 is merged to
devat1830177and pushed. The merged tree is identical to the verified head6932126. The path was:cli-auth(15a) andcli-mcp(15b), thencli-integrate(the semantic merge and credential design);fix-cli;verify-cli: fast, integration and browser all green; the four findings, the listener (TCP_NODELAYand the peer address) and the credential design all PASS.Decision on verify item 3 (FAIL). The one-second grant-shrink bound in
docs/contracts.md§2.3 is not proven: the regression test allows 7 s, and the helper can wait longer than 1 s. No run was observed over 1 s. This is a step 11 guarantee, not a step 15 regression, so it becomes step 11b (a separate issue) instead of blocking this merge.Still open for the owner: confirming the MCP credential design (
infraternal mcp setupmints a scopedai_agenttoken; no environment token).Later: the real-backend keychain integration test, and the timing of the first poll timer.
Next here: 15c, the CLI's
logs,execandsshover the stream.Starting step 15c (cli-stream2). Branch: cli-stream at
1830177ac5; fetched dev at7bda001e5b. I will merge current dev, then implement the CLI stream client, logs, exec, bounded MCP container_logs, and run the requested fast and integration gates.Finding: the existing ignored integration test
cli_and_mcp_use_the_public_http_contractalready starts real labeled Docker and rootless Podman containers, launches the compiled server and binary, and completesinfraternal loginwith a stored human CLI credential. I will extend this same real path for stream logs/exec and add a PTY check using portable-pty 0.9.0. ItsMasterPty::resizeAPI sets kernel winsize and notifies the child (https://docs.rs/portable-pty/latest/portable_pty/trait.MasterPty.html); crossterm 0.29.0 provides raw mode and terminal sizing (https://docs.rs/crossterm/latest/crossterm/terminal/).Protocol gap confirmed from the fetched dev base:
crates/proto/src/stream/controls.rsdefinesLogsParams {}with no tail/follow controls, andcrates/agent/src/lib.rs::run_logspassesLogOptions { follow: true, ..Default::default() }. For exec,ExecParamscontains dimensions/session fields only, andcrates/agent/src/lib.rs::run_execcallsengine.execwith a hardcoded/bin/sh; the public stream schema has neither command argv nor TTY/piped-mode selection. The existing stream client incrates/infraternalcan expose and exercise the channel, but protocol-correct non-follow/tail and direct command exec needcrates/protoplus infraternal-agent changes, outside this job’s allowed ownership. Per the job instruction I will not modify those crates.Evidence from the compiled CLI against the real Docker fixture (the same Docker/Podman integration test in this worktree): closing client stdin while
exec -iis attached through the current engine PTY path does not deliver EOF to a waiting process. Acatfixture stays open after local stdin closes and requires Ctrl-C. Closing stdin during an 8 MiB output fixture also closed the stream after 61,442 output bytes, so input half-close interrupts the output direction. The engine-sidePtySession::close_stdinshuts down its WebSocket stdin; it does not provide PTY EOF semantics.This needs a server/agent/engine change for the requested behavior: an exec transport that separates stdin half-close from output, and explicit argv/TTY handling in the stream open params. This branch is limited to
crates/infraternal, so I have not changed those crates. The client startup handshake now waits until terminal mode is configured before forwarding piped input.Finished cli-stream2 on branch
cli-streamat head95aed557266080a0ad0a0bddcb75f6b1f6b2b1fe(no push).Example output from the compiled CLI against the real Docker fixture:
The same byte-exact log and piped-exec checks passed against rootless Podman. Exec returned remote code 23 and preserved piped bytes including NUL and
0xff. The Docker slow-reader check delivered 8,388,613 bytes with 104 KiB observed RSS growth. The Podman test fixtures select the documentedk8s-filelogging driver because this VM's default journald setup has no events backend (Podman run docs).Follow-up blocker remains outside this branch's ownership: the stream contract lacks server-side logs tail/follow and exec argv/TTY parameters, and the current engine PTY path does not deliver protocol stdin EOF to a waiting process. The CLI sends EOF, but the real
catfixture remains open until Ctrl-C. I reported the evidence on this issue and did not change proto/server/agent/engine.scripts/verify fastoutput:scripts/verify integrationoutput:Review started: branch
cli-stream, base7bda001, reviewed head95aed557266080a0ad0a0bddcb75f6b1f6b2b1fe. Independent review of the step 15c CLI stream changes.Independent review of
cli-stream, base7bda001, head95aed557266080a0ad0a0bddcb75f6b1f6b2b1fe. Review only; no source edits or commits.Findings
blocks-merge —
exec -itdoes not restore raw mode on SIGTERM.exec_streamlistens for Ctrl-C and SIGWINCH, but has no SIGTERM listener (crates/infraternal/src/cli.rs:481-485,522-530). The raw-mode guard restores throughDrop(:688-715); default signal termination does not unwind and run Rust destructors (Rust Reference). The real PTY test covers ETX/Ctrl-C restoration only (crates/infraternal/tests/cli_mcp.rs:2041-2089). A SIGTERM whileexec -itis active can leave the user's TTY with echo and canonical input disabled.blocks-merge — Rust
StreamClientdoes not implement §3.10 client liveness.connection_controlreplies to incoming PING and accepts PONG (crates/infraternal/src/stream.rs:426-435), but the client has no idle timer or PING sender;next_eventwaits directly on the socket (:280-300). It therefore never sends its own PING after 15 seconds or closes after 45 seconds without a frame. A silently dead connection can leavelogs,exec, or MCP waiting indefinitely.blocks-merge — malformed frames and log records fail without the contract close indication.
read_framemapsFrame::decodeand sender-validation errors directly toStreamError::Protocol(crates/infraternal/src/stream.rs:439-451); the only explicit WebSocket protocol close is the text-message branch (:465-475). Oversize errors are mapped to a generic transport error (:487-493). The CLI log decoder propagates invalid/oversized-record errors (crates/infraternal/src/cli.rs:311-315) and MCP returns from the read path (crates/infraternal/src/mcp.rs:573-576) without sendingCLOSEwithinvalid_request/record_too_large. §3.2 requires connection close codes 1002/1009, and §3.7 requires the channel error close. Proto tests prove pre-allocation length validation, but there is no RustStreamClientmalformed-frame/record test.blocks-merge — log “snapshot” completion is guessed from a 2-second idle period.
open_logssends defaultLogsParams(crates/infraternal/src/stream.rs:131-148). The CLI treats a 2-second timeout as backlog completion and only then emits a requested tail (crates/infraternal/src/cli.rs:273-307); MCP resets the same idle timeout for each event (crates/infraternal/src/mcp.rs:564-568). With the current server's always-follow log stream, a continuously active container never becomes idle:logswithout--followand MCPcontainer_logsdo not return, and--tailwithholds records. That conflicts with the CLI's backlog behavior and the MCP description that it returns a snapshot and never follows live output. The result byte cap bounds retained data, but not the duration of the call.later — piped exec help overpromises stdin EOF.
ExecArgssays “piped mode always honors stdin EOF” (crates/infraternal/src/cli.rs:94), and the client sends protocol EOF at:517-520. But the real integration fixture explicitly checks whethercatremains alive two seconds after stdin closes and interrupts it if so (crates/infraternal/tests/cli_mcp.rs:1824-1860). This is the known server/PTY EOF gap tracked under step 11d, outside this branch; until that is fixed, the CLI should describe the limitation or fail clearly instead of claiming EOF is honored.blocks-merge — the new streaming hot path has no benchmark. The diff adds the Rust stream transport and CLI/MCP data paths but no benchmark. The real integration test checks slow-reader RSS and prints a command RTT smoke value; it does not benchmark throughput/latency or compare the change against the base on the perf-test VM. The operating contract requires a benchmark for each new hot path, and
docs/PLAN.mdrequires perf-test-VM A/B evidence for stream budgets.Checks that passed
crates/protoframe/channel/record types andFrame::decode; log headers are checked before body buffering. Receive/send credit and CLI tail retention are bounded.ApiClient::stored, so the existing stored credential and refresh path is used. I found no token accepted through argv/environment or emitted in tracing/CLI JSON.container_logshas an output schema, enforcesmax_bytesandtailcaps, opens alogschannel requiringcontainers:readon the server, and exposes no exec tool.scripts/verify fast:scripts/verify integration:Starting the step 15c fix round on branch
cli-stream. Base is currentdevat9688a2438a1c5bbeeb3326a5be8df58e5c4f9a0a, merged into the branch; the supplied branch head was95aed557266080a0ad0a0bddcb75f6b1f6b2b1fe.Review trace verified on merged head
09dd041:StreamClient::read_framesends a WebSocket close only for text messages;Frame::decodeand sender-validation errors return without a close.LogRecordDecoder::RecordError::error_code()already provides the requiredinvalid_request/record_too_largemapping, but both CLI and MCP currently use a 2-second idle timeout against a follow stream.Decision: keep the stream wire contract unchanged until step 11d (#29). CLI snapshots will use an absolute
--timeout(5 seconds by default without--follow) and say when the deadline truncated output. MCP snapshots will use a validated maximum duration and byte cap, and include truncation reasons in structured output. Liveness tests will inject a silent WebSocket peer with shortened test-only intervals; the real PTY test will cover SIGTERM, SIGHUP and SIGQUIT.The liveness regression test now injects a silent WebSocket peer after OPEN_OK and verifies the contract's 15 s/45 s defaults, a channel-0 PING, and the 4008 close. It also caught that PING carries the shared 8-byte
PingPayloadtimestamp; an empty PING is rejected by the frame codec. The malformed-frame tests verify close codes 1002 and 1009, and injected invalid/oversized log records verify channel CLOSE codesinvalid_requestandrecord_too_large.Real-engine CLI/MCP integration evidence: the continuous-output CLI snapshot stopped at its deadline, and the real PTY signal checks for SIGTERM, SIGHUP and SIGQUIT completed before the exec smoke check. The test then ran for 909.50s and the exec stream closed with WebSocket code 4003, consistent with the original device credential aging out during the long fixture sequence. I added a device login before each exec smoke round trip and a five-second assertion bound around both MCP log tool calls; the targeted integration is being rerun.
Finding from the real CLI/MCP integration rerun: the one-second
container_logscall intentionally returns a partial snapshot while the server is following a 40,000-record backlog. The previous assertion expected record 40,000 and failed after record 7,486; it depended on completion inside a hard deadline. The check now verifies every returned record is valid, output stays within 64 bytes, and structured truncation reasons include bothbyte_limitandduration_limit.Evidence:
cargo test -p infraternal --test cli_mcp -- --ignored --nocapture --test-threads=1passed:test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 112.40s.Verification update:
scripts/verify fastpasses. I beganscripts/verify integration, then found the parallellive-agentlane was already inscripts/verify benchwith a release compile active at the same time. I interrupted my integration run (exit 130) to respect the shared-VM rule allowing one full release build at a time; I will rerun the gate after that bench process exits.Second integration scheduling update: after
live-agent's benchmark gate exited and VM load fell below 10, I restartedscripts/verify integration. A release compile from/home/kayg/wt/shadcnthen started concurrently (cargo build --release -p infraternal), so I interrupted this attempt with exit 130 to keep one full release build active at a time. I will retry after that build exits and load is below 10.The first
scripts/verify benchattempt passed the setup, terminal ingest smoke, Bun hop diagnostic, and direct Docker/framed PTY hop step, then failed the pre-existing Chromium benchmark gate under concurrent CPU load. Evidence from/tmp/cli-stream-bench-final.log: shaped-logkeystroke_p99_ms=65.5exceeded the dev-VM budget of 10 ms; the gate reportedstream benchmark dev VM budget: keystroke p99 65.500 ms > 10 ms.At the time, eight
stress-ng-cpuworkers were active (stress-ng --cpu 8 --cpu-load 100 --timeout 50m --metrics-brief, started by another worktree). I will retry the gate and the new Rust CLI stream benchmark after that load ends; the new named bench step was not reached in this failed run.Environment update: the initial 50-minute CPU stress run has ended, but the same external worktree immediately started another eight-worker run with a 30-minute timeout (
stress-ng --cpu 8 --cpu-load 100). At 14:17 UTC it is active and load is 9.6/11.4/12.2. The clean bench retry and new CLI stream smoke remain pending until all averages fall below 10.Scheduling update: after the previous 30-minute stress run ended, another external worktree began a new 45-minute
stress-ng --cpu 8 --cpu-load 10run and restartedscripts/verify integrationwith a release build. Load is again above 10, so I am holding the benchmark retry until this release/browser work and load clear.Second
scripts/verify benchretry: the Chromium contractual gate passed with shaped-log p99 8.0 ms against the 10 ms limit, and the existing Criterion steps passed through the server container route benchmark. The subsequent 500-container CLI/API benchmark failed atcrates/infraternal/benches/containers.rs:279: the HTTP request returned 401 instead of 200. Its preceding smoke output wasMETRIC ps 500 HTTP/TCP API smoke ... p50=3704µs p99=11081µsandMETRIC ps 500 compiled CLI/TCP smoke ... p50=44ms p99=95ms.That file is owned by the parallel
live-agentlane, so it remains untouched. This failure preventsscripts/verify benchfrom reaching the new CLI stream benchmark step. I will smoke that named step directly after the active external release/integration jobs clear.Fix round complete at head
3eea6dc5cd4241ae85f40f7816d63d865d6c65c2on branchcli-stream. No push; issue remains open.Finding status:
stty -gbefore/after and assert 143/129/131.invalid_requestandrecord_too_large; injected tests cover each.--timeout(default 5 seconds) and a stderr truncation note; MCP has a hard duration and byte cap with structured truncation reasons. Continuous-output tests pass.scripts/verify benchstep. The full gate stopped earlier in the parallel-owned 500-container benchmark; standalone stream bench output is below.scripts/verify fast(exit 0)scripts/verify integration(exit 0)scripts/verify bench(exit 1)The failed 500-container benchmark belongs to the parallel lane and was left untouched. It failed at
crates/infraternal/benches/containers.rs:279with HTTP 401 instead of 200 after its initial HTTP/CLI smoke metrics; this stopped the script before the new stream step.Standalone Rust CLI stream benchmark smoke (exit 0)
The bench gate stopped in the parallel-owned
crates/infraternal/benches/containers.rs:279after the compiled CLI received HTTP 401 where that benchmark expected 200. I did not modify that file. The new stream benchmark passed independently as shown above. No push.Orchestrator: the 15c fix round is done at
3eea6dc(fast and integration pass).benchstops atcrates/infraternal/benches/containers.rs:279(401): that credential fix is on branchcontainer-live-agent(2a9db22), which arrives with step 13b. Order: 13b merges first, then the 15c verify mergesdevand runs all gates, includingbench.Starting step 15c verification on branch
cli-stream. Worktree starts at3eea6dc; fetched targetdevbase isee62153839982d068586e3890bfe8749db8f1dab. I will merge that head first, then verify each review finding and run the required gates when load5 is below 8.Verification finding:
scripts/verify faststopped atcargo clippyafter thedevmerge. Rust reported E0063 atcrates/infraternal/src/stream.rs:791and:919: the two CLI stream test fixtures initializeOpenOkPayloadwithout the newly requiredreplay_startfield. I will fix these test fixtures only, using the field's contract value, then rerun the gates.Step 15c verification
Branch:
cli-streamHead:
b80d1258387ca6af7f17b28c9659cb2a194db64dAll six review checks PASS:
stty -gbefore and after each signal and asserts exit codes 143, 129, and 131.scripts/verify integrationpassed the compiled CLI/MCP real-server test.silent_server_gets_ping_then_liveness_close_4008sends a channel-0 PING to a silent peer, then observes close 4008. The client reports the 45-second no-frame timeout with stable exit code 7; the test uses shortened intervals and asserts the production 15/45-second defaults.invalid_requestandrecord_too_largefor bad/oversized log records.logs --timeout 1returns under 5 seconds during continuous output and writes the truncation note to stderr. MCPcontainer_logsreturns within the hard duration withtruncated: trueandduration_limitintruncation_reasons.scripts/verify benchran the real-server Rust CLI stream Criterion target and passed the compiled CLI/TCP benchmark with 500 real containers (crates/infraternal/benches/containers.rs). The default dev-VM timing output is marked non-reportable by the gate.The first post-merge
fastrun found two test fixture initializers missing optionalOpenOkPayload.replay_start. I set both toNonein commitb80d125; the final fast rerun passed.Focused stream tests:
scripts/verify fast(exit 0), output verbatim:scripts/verify integration(exit 0), output verbatim:scripts/verify bench(exit 0, defaultdevprofile), output verbatim:Orchestrator: step 15c is merged to
devatcd275a4and pushed; the tree is identical to the verifiedb80d125(fast 15/15, integration 13/13, bench green).infraternal sshwaits for 14b.Starting CLI SSH work on branch
cli-ssh, based on2444ab2fad493cdc05250587f96d941a2771d1f5(step 14b SSH backend). Scope iscrates/infraternal: hosts SSH display and trust, persistent SSH stream CLI with replay/resize/raw terminal handling, session list/close, MCP host trust state and non-interactive SSH documentation. I’m reviewing the new proto/contracts and will report any missing server capability here.Verified the existing public API supports the CLI path without a server/proto change:
GET /api/v1/hosts/{host_id}/sshreturns SSH settings, andGET /api/v1/hosts/{host_id}/ssh/host-keyprobes and returns presented/trusted fingerprints. The host list proto itself has no SSH fields, so CLI/MCP inventory must enrich rows through those documented endpoints. The trust POST probes again and rejects if either the presented fingerprint or trust state changed; the server does not accept a client-supplied public key. Stream setup is a bodyless ticket POST followed by theinfraternal.v1WebSocket subprotocol; SSH OPEN requires a persistentopen_keyor an existingsession_idand carries replay offsets in OPEN_OK.Finding: the real PTY test reached the server's SSH channel CLOSE after the remote shell exited, but the local CLI process stayed alive because Tokio stdin can leave an uncancellable blocking read running during runtime shutdown. Interactive and pollable pipe input now use a nonblocking AsyncFd reader; its original fd flags are restored before crossterm restores raw mode. The compiled CLI integration test passed with remote exit code 37, resized dimensions 41x111, detach/reattach replay, changed-key refusal, and session list/close.
The #27 sshd fixture's private helper tests register as tests when its source is imported directly into cli_mcp, so the CLI test uses the same fixture body with only that private test module omitted and the reason documented next to the copy.
Commits:
5b354cf,e71a7be.scripts/verify fastpassed. Integration is being rerun.Finished step 15d (
infraternal ssh). Branch:cli-ssh; base:2444ab2fad493cdc05250587f96d941a2771d1f5; head:e71a7beeeb1af315b790d585bbc0867b818270a5.Implemented SSH settings and trust state in
hosts, trust-key preflight/reprobe with fingerprint matching and ai_agent dry-run/plan controls, persistent SSH terminal sessions with SIGWINCH resize, signal-safe terminal restoration, replay attach and remote exit status, sessions list/close, and MCP host trust status/planned trust tool. MCP docs state that MCP has no interactive SSH terminal. Changed-key refusal reports trusted and presented fingerprints and never auto-trusts.The compiled CLI/MCP integration against the real server and isolated
sshdverified trust/refusal, command output, PTY resize (41 111), terminal restoration, remote exit status37, detach/attach replay, changed-key refusal, MCP trust plan execution, and session list/close. Example PTY session output (the session ID varies):infraternal hostsdisplays SSH address, port, user, credential, shell mode, and trust state/fingerprints. A custom shell is shown ascustomwithout exposing its configured text.scripts/verify fastoutput:scripts/verify integrationoutput:Independent review and verification for step 15d (
cli-ssh).Branch:
cli-sshBase reviewed:
a5fec35ee358542e471fd2ad9e019e84dd458bc0(current dev merged first)Final head:
b8fe4e025dd7572b6111ea49d7ba0cfbee74b9bbFindings:
b8fe4e0: SSH signal cleanup awaited the WebSocket CLOSE write before starting its acknowledgement timeout. A stalled write could leave the local terminal in raw mode indefinitely. The write and acknowledgement now share the 250 ms timeout. Added a stalled-sink regression test and real PTY signal coverage. No blocks-merge findings remain open.Checks:
ai_agentdestructive flow exposes--dry-runand--plan. Real SSH integration covers trust and changed-key refusal; MCP plan handling is also exercised.--jsonoutput. The CLI uses the stored credential reference and authenticated API flow; JSON output contains session/replay metadata only.sshd. Changed-key refusal is covered.scripts/verify fast:scripts/verify integration(started with load5 below 8):Orchestrator: step 15d (CLI SSH) is merged to
devat84d798eand pushed; the tree is identical to the reviewedb8fe4e0(fast 15/15, integration 14/14). Later: a PTY test that forces a peer TCP drop while the terminal is raw.