diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index 4c4e428..2e6742b 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1,5 +1,5 @@ { - "task_file": "dev/tasks/DEV-000059-local-cognitive-canary-observation.json", - "task_id": "DEV-000059", + "task_file": "dev/tasks/DEV-000060-llm-free-provider-env-audit.json", + "task_id": "DEV-000060", "task_revision": 1 } diff --git a/dev/tasks/DEV-000060-llm-free-provider-env-audit.json b/dev/tasks/DEV-000060-llm-free-provider-env-audit.json new file mode 100644 index 0000000..a203642 --- /dev/null +++ b/dev/tasks/DEV-000060-llm-free-provider-env-audit.json @@ -0,0 +1,23 @@ +{ + "task_id":"DEV-000060", + "task_revision":1, + "architecture_revision":"ADR-0008", + "base_repo_revision":"bcb106e0a37a8620c5f06f3c7d32ff682839037e", + "risk_tier":"E", + "intent":"Correct the LLM-free production audit so it distinguishes explicitly declared non-secret cognitive provider configuration from prohibited plaintext credentials, without changing runtime routing, binding lifecycle, or secret-isolation policy.", + "requirement_ids":["INV-EPI-001","ADR-0006","ADR-0008","LLM-FREE-PROVIDER-ENV-AUDIT"], + "allowed_change_scope":{"modules":[],"paths":["tests/cognitive_advisory/**","docs/PROJECT_STATUS.md","dev/ACTIVE_TASK.json","dev/tasks/DEV-000060-llm-free-provider-env-audit.json"]}, + "forbidden_change_scope":{"paths":["architecture/**","contracts/**","modules/**","adapters/**","database/**","deploy/**","cmd/**","security/**","ci/**",".gitea/**"]}, + "allowed_architecture_delta":{"new_cross_module_dependencies":0,"new_canonical_owners":0,"new_privileges":0,"new_third_party_dependencies":0,"new_provider_adapters":0}, + "contracts":{"consume":["cognitive-worker-runtime-environment","CredentialRef secret-file policy"],"may_change":[]}, + "semantic_owner":"none; production audit guardrail only", + "canonical_state":[], + "state_transitions":["none; CANARY and ACTIVE binding states are not modified"], + "side_effects_and_privileges":["read-only production container inspection and advisory audit only","no credential creation or exposure","no provider invocation except the existing fail-closed audit request"], + "persistence_and_migrations":["none"], + "failure_retry_restart":["unknown environment names fail closed","plain credential-like environment names fail closed","only exact CredentialRef file names under /run/secrets are allowed"], + "assumptions":["LOCAL_CPU_PROVIDER_URL and LOCAL_OLLAMA_PROVIDER_URL are non-secret endpoint configuration","docker-test remains prohibited"], + "acceptance_scenarios":["LOCAL_CPU_PROVIDER_URL passes","LOCAL_OLLAMA_PROVIDER_URL passes","OPENAI_API_KEY fails","PROVIDER_TOKEN fails","OPENAI_API_KEY_FILE=/run/secrets/... passes","unknown credential-like environment fails closed","production worker exact environment passes"], + "required_tests":["PowerShell environment-policy regression suite","LLM-free production audit","go test ./...","go vet ./...","architecture sentinel","provider/router tests","exact diff scope"], + "terminal_gap_states":["BLOCKED_BY_ARCHITECTURE_GAP","BLOCKED_BY_SPEC_GAP","BLOCKED_BY_MISSING_CAPABILITY","BLOCKED_BY_CONFLICT"] +} diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index 8260204..8ed011c 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -1,6 +1,6 @@ # Assistant project status -Audit date: 2026-08-17. Active implementation: DEV-000059 revision 1. +Audit date: 2026-08-17. Active implementation: DEV-000060 revision 1. ## Current milestone @@ -14,6 +14,18 @@ Audit date: 2026-08-17. Active implementation: DEV-000059 revision 1. - DEV-000058 promoted the exact CPU `Qwen2.5-3B-Instruct-Q4_K_M` lineage to `VALIDATED/CANARY` through an authenticated, evidence-gated, append-only CAS transition. Ordinary routing still excludes it; only an explicit `AllowCanary` request can select it. All other current bindings remain `INACTIVE`. - DEV-000059 enables that exact canary behind a fail-closed cognitive-worker flag and a 4 KiB output clamp. Five authenticated synthetic Creator requests completed through the full production path with typed usage observations, zero failures and zero API cost. The binding remains `CANARY`, not `ACTIVE`. +## Cognitive Foundation baseline + +- `COGNITIVE_FOUNDATION = COMPLETED` +- `PRODUCTION_REGISTRY = RUNNING` +- `LOCAL_CPU_CANARY = WORKING` +- `CANARY_SUCCESSFUL_REQUESTS = 5` +- `ACTIVE_BINDINGS = none` +- `OPENAI_REAL_CREDENTIAL = not required / not activated` +- `NEXT_MILESTONE = Human Interaction + Intent/Evidence Semantics + Project Copilot Foundation` + +DEV-000060 corrects the LLM-free production audit with an exact, fail-closed cognitive-worker environment allowlist. Non-secret local provider endpoint variables are permitted only by name; plaintext credentials remain prohibited and secret-file references are restricted to the existing `OPENAI_API_KEY_FILE` mechanism under `/run/secrets`. + ## Delivered - Foundation V1 is frozen and all exit criteria pass. diff --git a/tests/cognitive_advisory/cognitive_worker_env_policy.ps1 b/tests/cognitive_advisory/cognitive_worker_env_policy.ps1 new file mode 100644 index 0000000..63cb3e7 --- /dev/null +++ b/tests/cognitive_advisory/cognitive_worker_env_policy.ps1 @@ -0,0 +1,58 @@ +Set-StrictMode -Version Latest + +$script:AllowedCognitiveWorkerEnvironment = @{ + 'CORE_RUNTIME_URL' = 'http_url' + 'LOCAL_CPU_PROVIDER_URL' = 'http_url' + 'LOCAL_GPU_PROVIDER_URL' = 'http_url' + 'LOCAL_OLLAMA_PROVIDER_URL' = 'http_url' + 'COGNITIVE_OWNER_PRINCIPAL_ID' = 'nonempty' + 'COGNITIVE_ALLOW_CANARY' = 'boolean' + 'LISTEN_ADDR' = 'listen_address' + 'OPENAI_API_KEY_FILE' = 'secret_file' + 'PATH' = 'nonempty' +} + +function Test-CognitiveWorkerEnvironmentPolicy { + [CmdletBinding()] + param([Parameter(Mandatory)][string[]]$Environment) + + foreach ($entry in $Environment) { + $parts = $entry -split '=', 2 + if ($parts.Count -ne 2 -or [string]::IsNullOrWhiteSpace($parts[0])) { + throw "Cognitive worker environment entry is malformed: $entry" + } + + $name = $parts[0] + $value = $parts[1] + if (-not $script:AllowedCognitiveWorkerEnvironment.ContainsKey($name)) { + if ($name -match '(?i)(API_KEY|TOKEN|PASSWORD|SECRET|CREDENTIAL|ACCESS_KEY|PRIVATE_KEY)') { + throw "Cognitive worker credential-like environment is not an approved CredentialRef file: $name" + } + throw "Cognitive worker environment is not explicitly approved: $name" + } + + switch ($script:AllowedCognitiveWorkerEnvironment[$name]) { + 'http_url' { + $uri = $null + if (-not [uri]::TryCreate($value, [System.UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -notin @('http', 'https') -or [string]::IsNullOrWhiteSpace($uri.Host)) { + throw "Cognitive worker endpoint is invalid: $name" + } + } + 'boolean' { + if ($value -notin @('true', 'false')) { throw "Cognitive worker boolean is invalid: $name" } + } + 'listen_address' { + if ($value -notmatch '^:[0-9]{1,5}$') { throw "Cognitive worker listen address is invalid: $name" } + } + 'secret_file' { + if ($value -notmatch '^/run/secrets/[A-Za-z0-9][A-Za-z0-9._-]*$') { + throw "Cognitive worker CredentialRef file is invalid: $name" + } + } + 'nonempty' { + if ([string]::IsNullOrWhiteSpace($value)) { throw "Cognitive worker environment is empty: $name" } + } + default { throw "Cognitive worker environment policy is invalid for: $name" } + } + } +} diff --git a/tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1 b/tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1 new file mode 100644 index 0000000..ea7bdc7 --- /dev/null +++ b/tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1 @@ -0,0 +1,24 @@ +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +. "$PSScriptRoot/cognitive_worker_env_policy.ps1" + +function Assert-PolicyPass([string]$Name, [string[]]$Environment) { + try { Test-CognitiveWorkerEnvironmentPolicy -Environment $Environment } + catch { throw "$Name expected PASS: $($_.Exception.Message)" } +} + +function Assert-PolicyFail([string]$Name, [string[]]$Environment) { + try { Test-CognitiveWorkerEnvironmentPolicy -Environment $Environment } + catch { return } + throw "$Name expected FAIL" +} + +Assert-PolicyPass 'CPU provider endpoint' @('LOCAL_CPU_PROVIDER_URL=http://docker.cin.su:18082/v1') +Assert-PolicyPass 'Ollama provider endpoint' @('LOCAL_OLLAMA_PROVIDER_URL=http://192.168.220.91:11434') +Assert-PolicyFail 'plaintext OpenAI API key' @('OPENAI_API_KEY=secret') +Assert-PolicyFail 'plaintext provider token' @('PROVIDER_TOKEN=secret') +Assert-PolicyPass 'approved OpenAI CredentialRef file' @('OPENAI_API_KEY_FILE=/run/secrets/openai_api_key') +Assert-PolicyFail 'unknown credential-like variable' @('UNRECOGNIZED_API_KEY_FILE=/run/secrets/unknown') + +Write-Output 'Cognitive worker environment policy regression tests: PASS' diff --git a/tests/cognitive_advisory/verify-remote.ps1 b/tests/cognitive_advisory/verify-remote.ps1 index c127473..59da669 100644 --- a/tests/cognitive_advisory/verify-remote.ps1 +++ b/tests/cognitive_advisory/verify-remote.ps1 @@ -10,6 +10,8 @@ param( $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest +. "$PSScriptRoot/cognitive_worker_env_policy.ps1" + function Get-ContainerEnvironment([string]$Container) { $result = @{} $inspect = ((& docker.exe --context $DockerContext inspect $Container) -join "`n") | ConvertFrom-Json @@ -72,8 +74,7 @@ $injected = Invoke-StatusRequest "$BaseURL/api/advisory" $authorization $injecte if ($injected.StatusCode -ne 400) { throw "Authority-field injection status=$($injected.StatusCode)" } $cognitiveInspect = ((& docker.exe --context $DockerContext inspect $CognitiveContainer) -join "`n") | ConvertFrom-Json -$forbiddenEnvironment = @($cognitiveInspect[0].Config.Env | Where-Object { ($_ -split '=', 2)[0] -match '(?i)(POSTGRES|DATABASE|COMMIT|EXECUTOR|CREATOR|PASSWORD|TOKEN|PROVIDER|API_KEY)' }) -if ($forbiddenEnvironment.Count -ne 0) { throw 'Cognitive worker has forbidden privileged/provider environment' } +Test-CognitiveWorkerEnvironmentPolicy -Environment @($cognitiveInspect[0].Config.Env) $forbiddenMounts = @($cognitiveInspect[0].Mounts | Where-Object { $_.Destination -match '(?i)(secret|credential|token|postgres|docker)' }) if ($forbiddenMounts.Count -ne 0) { throw 'Cognitive worker has a forbidden credential or infrastructure mount' } $recentLogs = ((& docker.exe --context $DockerContext logs --since 5m $CognitiveContainer 2>&1) -join "`n")