From 5b24f5c4287fbeabef200fb5319ef72c0124d03b Mon Sep 17 00:00:00 2001 From: Remylus Losius Date: Mon, 15 Jun 2026 06:06:47 -0400 Subject: [PATCH] fix(spec): correct agent env-var drift to match shipped P-011 behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent specs still described the original L-014b opt-in mechanism (KENSA_USE_AGENT=1, default direct-SSH), but P-011 (Q1.c ratified 2026-05-12) flipped agent-mode to the default and replaced the flag with the opt-out KENSA_NO_AGENT=1. The code and tests shipped that change; the specs and CLAUDE.md did not follow — spec/code drift the strict-coverage gate can't catch (it maps tests to ACs by annotation id, not by checking prose against code). agent-cli-env-var (0.1.0 -> 0.2.0, the semantics flipped): - context/objective/scope/C-04/C-06/AC-04 now describe agent-mode as the default with KENSA_NO_AGENT=1 as the opt-out; C-04 inverts the gating to `os.Getenv("KENSA_NO_AGENT") != "1"`; C-06's announce string matches the shipped `agent mode (default; unset with KENSA_NO_AGENT=1) ...`. - AC-04 dropped its reference to TestRunRemediate_KensaUseAgent_Detection (removed when the flag flipped) and now points at the real gating site in cmd/kensa/main.go and the announce-line assertion in TestOpenAgent_AnnounceLine. - A historical note records the L-014b -> P-011 sense reversal (the two remaining KENSA_USE_AGENT mentions are that history, like the CHANGELOG Breaking-changes entry). agent-handler-port-filepermissions (reference correction, no contract change, version unchanged): - KENSA_USE_AGENT -> the agent-mode-default phrasing in objective + scope. - cmd/kensa/remediate.go -> cmd/kensa/main.go (remediate.go does not exist; the gating lives in main.go). Spec-only, prose-level: no AC ids renamed, no test annotations touched, so coverage mapping is unaffected. specter 119/119; both specs PASS at T2 (7/7 and 9/9). No code change. Co-Authored-By: Claude Opus 4.8 (1M context) --- specs/agent/cli-env-var.spec.yaml | 79 +++++++++++-------- .../handler-port-filepermissions.spec.yaml | 6 +- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/specs/agent/cli-env-var.spec.yaml b/specs/agent/cli-env-var.spec.yaml index 17128ee..0aa852f 100644 --- a/specs/agent/cli-env-var.spec.yaml +++ b/specs/agent/cli-env-var.spec.yaml @@ -1,6 +1,6 @@ spec: id: agent-cli-env-var - version: 0.1.0 + version: 0.2.0 status: draft tier: 2 @@ -8,15 +8,25 @@ spec: system: kensa feature: agent-cli-env-var description: | - LL Phase 1 deliverable L-014b. The - CLI-lifecycle plumbing partitioned out of L-014: - `kensa remediate` invoked with - `KENSA_USE_AGENT=1` bootstraps the target-side - kensa binary, spawns `ssh - agent --stdio`, opens an AgentClient, runs the - version handshake, and passes - `engine.WithAgentClient` so the remediate flow - dispatches through the agent. + LL Phase 1 deliverable L-014b, with the env-var sense + reversed by P-011 (Q1.c ratified 2026-05-12). `kensa + remediate` dispatches through the target-side agent **by + default**: it bootstraps the target kensa binary, spawns + `ssh agent --stdio`, opens an + AgentClient, runs the version handshake, and passes + `engine.WithAgentClient` so the remediate flow dispatches + through the agent. An operator opts OUT with + `KENSA_NO_AGENT=1`, which falls back to the direct-SSH + transport (shell-pipeline best-effort atomicity for the + file mechanisms). + + **P-011 sense reversal.** L-014b originally shipped this + as opt-IN via `KENSA_USE_AGENT=1` (default was direct-SSH). + P-011 made agent-mode the default and replaced the opt-in + flag with the opt-out `KENSA_NO_AGENT=1`, because kensa is + pre-production and the kernel-atomic path is the safer + default. There is no deprecation period (pre-1.0); the old + `KENSA_USE_AGENT` flag is gone, not aliased. **Why partitioned from L-014.** L-014 was the architectural bridge (LocalTransport, @@ -41,8 +51,8 @@ spec: The cleanup function tears down the subprocess on engine completion (or error). - 3. `cmd/kensa/main.go` runRemediate — when - `KENSA_USE_AGENT=1` is set, calls + 3. `cmd/kensa/main.go` runRemediate — by default + (unless `KENSA_NO_AGENT=1` is set) calls dispatcher.OpenAgent first, then DefaultWithEngineOptions(... , engine.WithAgentClient(c)). @@ -60,15 +70,15 @@ spec: summary: | Add DefaultWithEngineOptions to pkg/kensa. Add dispatcher.OpenAgent lifecycle wrapper. - Wire KENSA_USE_AGENT=1 in runRemediate. Unit - tests + spec. + Wire agent-mode-as-default (opt out with + KENSA_NO_AGENT=1) in runRemediate. Unit tests + spec. scope: includes: - "pkg/kensa/kensa.go: DefaultWithEngineOptions" - "pkg/kensa/kensa_test.go: option-aware factory test" - "internal/agent/dispatcher/setup.go: OpenAgent lifecycle" - "internal/agent/dispatcher/setup_test.go: lifecycle test using a fake ssh-binary that just exec's kensa locally" - - "cmd/kensa/main.go: KENSA_USE_AGENT=1 detection in runRemediate; stderr log of the agent-mode path firing" + - "cmd/kensa/main.go: KENSA_NO_AGENT opt-out detection in runRemediate (agent-mode is the default); stderr log of the active path" excludes: - "Live-host parity test: gated on KENSA_TEST_SSH_HOST" - "kensa check --use-agent: only remediate gets the env-var path today (check is read-only; agent-mode value is less clear; defer)" @@ -116,9 +126,9 @@ spec: enforcement: error - id: C-04 description: | - `cmd/kensa/main.go runRemediate` MUST check - `os.Getenv("KENSA_USE_AGENT") == "1"` and, - when set, take the agent-mode path: + `cmd/kensa/main.go runRemediate` MUST take the + agent-mode path BY DEFAULT — that is, whenever + `os.Getenv("KENSA_NO_AGENT") != "1"`: 1. Construct a host-specific SSH transport for bootstrap (using existing internal/transport/ssh code). @@ -129,10 +139,11 @@ spec: 5. Run normally; produces an identical api.RemediationResult to direct-SSH (modulo per-step timing). - `KENSA_USE_AGENT` values other than "1" - (unset, "0", "true", etc.) take the - existing direct-SSH path. Strict "1" matching - avoids false positives on misset values. + Only `KENSA_NO_AGENT=1` (strict "1" match) takes + the direct-SSH path; any other value (unset, "0", + "true", etc.) keeps the agent-mode default. Strict + "1" matching avoids a misset value silently + disabling the safer default. type: business enforcement: error - id: C-05 @@ -150,12 +161,11 @@ spec: description: | OpenAgent MUST log to stderr ONE line announcing the agent-mode path fired: - `"kensa: agent mode (KENSA_USE_AGENT=1): - bootstrap+spawn+handshake completed for - host "`. Operators tailing stderr see - the agent-mode firing without ambiguity vs - direct-SSH (which produces no equivalent - line). + `"kensa: agent mode (default; unset with + KENSA_NO_AGENT=1): bootstrap+spawn+handshake + completed for host "`. Operators tailing + stderr see the agent-mode firing without ambiguity + vs direct-SSH (which produces no equivalent line). type: business enforcement: error @@ -187,11 +197,14 @@ spec: priority: critical - id: AC-04 description: | - `KENSA_USE_AGENT=1` flips runRemediate into - the agent path; unset/0 takes direct-SSH. - Tested via runCLI harness with a stubbed - host. Locked by - `TestRunRemediate_KensaUseAgent_Detection`. + runRemediate takes the agent path by default; + `KENSA_NO_AGENT=1` flips it to direct-SSH. The + agent path's firing is observable on stderr via + the announce line (see AC-06 / + `TestOpenAgent_AnnounceLine`, which asserts the + `KENSA_NO_AGENT=1` opt-out hint), and the gating + logic lives in `cmd/kensa/main.go` runRemediate + (`useAgent := os.Getenv("KENSA_NO_AGENT") != "1"`). references_constraints: [C-04] priority: high - id: AC-05 diff --git a/specs/agent/handler-port-filepermissions.spec.yaml b/specs/agent/handler-port-filepermissions.spec.yaml index 8c88eb4..1f87059 100644 --- a/specs/agent/handler-port-filepermissions.spec.yaml +++ b/specs/agent/handler-port-filepermissions.spec.yaml @@ -62,7 +62,7 @@ spec: 5. **CLI wiring deferred to L-014b sub-deliverable.** The `agent-mode default (unset KENSA_NO_AGENT)` env var path in - `cmd/kensa/remediate.go` plus the + `cmd/kensa/main.go` plus the bootstrap→ssh→client.Open→Handshake plumbing is invasive enough that splitting it from the L-014 architectural pieces (which are @@ -112,7 +112,7 @@ spec: summary: | Add LocalTransport + RemoteHandler + agent server.Handle + engine.WithAgentClient + CLI - KENSA_USE_AGENT path. End-to-end: + agent-mode-default (KENSA_NO_AGENT opt-out) path. End-to-end: file_permissions Apply / Capture / Rollback through the agent produces the same api.RemediationResult as the direct-SSH path. @@ -122,7 +122,7 @@ spec: - "internal/agent/remotehandler/remotehandler.go + _test.go" - "internal/agent/server/server.go + _test.go" - "internal/engine/engine.go: WithAgentClient option" - - "cmd/kensa/remediate.go: KENSA_USE_AGENT path" + - "cmd/kensa/main.go: agent-mode-default (KENSA_NO_AGENT opt-out) path" - "Integration test: local-process agent (bypasses bootstrap+SSH for testability)" - "Real-host live test: gated on KENSA_TEST_SSH_HOST + KENSA_TEST_AGENT_MODE=1" excludes: