Compare commits

...
Author SHA1 Message Date
m d03e9d3307 docs(evidence): record live FAST gateway smoke
CI / test (push) Successful in 31s
CI / test (pull_request) Successful in 31s
2026-08-22 17:43:37 +03:00
m 283f5c044c Merge pull request 'chore(deploy): wire optional cognitive provider secret volume' (#126) from codex/fleet-secret-wiring into main
CI / test (push) Successful in 41s
2026-08-22 14:28:33 +00:00
m ea5327799a chore(deploy): wire optional cognitive provider secret volume
CI / test (push) Successful in 25s
CI / test (pull_request) Successful in 25s
2026-08-22 17:27:29 +03:00
m dbbc54171d Merge pull request 'feat(cognitive): resolve local provider key from secret ref' (#125) from codex/cognitive-cpu-auth into main
CI / test (push) Successful in 33s
2026-08-22 14:22:55 +00:00
m 2b98501a68 Merge origin/main before cognitive provider secret release
CI / test (push) Successful in 26s
CI / test (pull_request) Successful in 26s
2026-08-22 17:21:02 +03:00
m d082005b37 feat(cognitive): resolve local provider key from secret ref
CI / test (push) Successful in 27s
CI / test (pull_request) Successful in 26s
2026-08-22 17:19:04 +03:00
m 624c41d4f6 Merge pull request 'feat: expose agent worker capability status' (#124) from codex/wp-804-worker-status into main
CI / test (push) Successful in 37s
2026-08-22 14:09:26 +00:00
12 changed files with 250 additions and 6 deletions
+11 -1
View File
@@ -22,9 +22,16 @@ import (
type OpenAICompatibleLocalAdapter struct {
base *url.URL
client *http.Client
apiKey string
}
func NewOpenAICompatibleLocalAdapter(raw string, transport http.RoundTripper) (*OpenAICompatibleLocalAdapter, error) {
return NewOpenAICompatibleLocalAdapterWithAPIKey(raw, "", transport)
}
// NewOpenAICompatibleLocalAdapterWithAPIKey creates an adapter for a gateway
// whose bearer credential was resolved by the trusted runtime boundary.
func NewOpenAICompatibleLocalAdapterWithAPIKey(raw, apiKey string, transport http.RoundTripper) (*OpenAICompatibleLocalAdapter, error) {
base, err := url.Parse(raw)
if err != nil || base.Scheme != "http" || base.Host == "" || strings.TrimSuffix(base.Path, "/") != "/v1" || base.RawQuery != "" || base.Fragment != "" || base.User != nil {
return nil, errors.New("invalid local compatible endpoint")
@@ -32,7 +39,7 @@ func NewOpenAICompatibleLocalAdapter(raw string, transport http.RoundTripper) (*
if transport == nil {
transport = http.DefaultTransport
}
return &OpenAICompatibleLocalAdapter{base: base, client: &http.Client{Transport: transport, Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}}, nil
return &OpenAICompatibleLocalAdapter{base: base, client: &http.Client{Transport: transport, Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}, apiKey: strings.TrimSpace(apiKey)}, nil
}
type compatibleModelsResponse struct {
@@ -145,6 +152,9 @@ func (a *OpenAICompatibleLocalAdapter) requestJSON(ctx context.Context, method,
return 0, err
}
request.Header.Set("Accept", "application/json")
if a.apiKey != "" {
request.Header.Set("Authorization", "Bearer "+a.apiKey)
}
if input != nil {
request.Header.Set("Content-Type", "application/json")
}
@@ -0,0 +1,42 @@
package models
import (
"context"
"io"
"net/http"
"strings"
"testing"
)
type captureRoundTripper struct {
authorization string
}
func (t *captureRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
t.authorization = request.Header.Get("Authorization")
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(`{"data":[]}`)), Header: make(http.Header), Request: request}, nil
}
func TestOpenAICompatibleLocalAdapterUsesResolvedAPIKey(t *testing.T) {
transport := &captureRoundTripper{}
adapter, err := NewOpenAICompatibleLocalAdapterWithAPIKey("http://gateway/v1", "resolved-key", transport)
if err != nil {
t.Fatal(err)
}
_, _ = adapter.Discover(context.Background())
if transport.authorization != "Bearer resolved-key" {
t.Fatalf("unexpected authorization header: %q", transport.authorization)
}
}
func TestOpenAICompatibleLocalAdapterDoesNotSendAuthorizationWithoutKey(t *testing.T) {
transport := &captureRoundTripper{}
adapter, err := NewOpenAICompatibleLocalAdapter("http://gateway/v1", transport)
if err != nil {
t.Fatal(err)
}
_, _ = adapter.Discover(context.Background())
if transport.authorization != "" {
t.Fatalf("unexpected authorization header")
}
}
+33 -2
View File
@@ -10,6 +10,8 @@ import (
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/identity"
secretcontracts "git.cin.su/m/Assistant/contracts/secrets"
platformsecrets "git.cin.su/m/Assistant/platform/secrets"
"git.cin.su/m/Assistant/runtime/health"
"io"
"log"
@@ -43,11 +45,21 @@ func main() {
discoveries := map[string]providerRegistration{}
var builtinLocal *providerRegistration
externalRecovery := []providerRegistration{}
cpuAdapter, err := models.NewOpenAICompatibleLocalAdapter(envOr("LOCAL_CPU_PROVIDER_URL", "http://docker.cin.su:18082/v1"), nil)
cpuAPIKey, err := loadLocalCPUProviderAPIKey()
if err != nil {
log.Fatal(err)
}
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, DataPolicy: capability.DataPolicyLocalOnly, Provider: cpuAdapter, Discovery: cpuAdapter}
cpuAdapter, err := models.NewOpenAICompatibleLocalAdapterWithAPIKey(envOr("LOCAL_CPU_PROVIDER_URL", "http://docker.cin.su:18082/v1"), cpuAPIKey, nil)
if err != nil {
log.Fatal(err)
}
cpuAuthMode := capability.AuthNone
cpuCredentialRef := capability.CredentialRef("")
if cpuAPIKey != "" {
cpuAuthMode = capability.AuthKey
cpuCredentialRef = capability.CredentialRef(envOr("LOCAL_CPU_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-cpu/provider-api-key/v1"))
}
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", CredentialRef: cpuCredentialRef, AuthMode: cpuAuthMode, BillingMode: capability.BillingFree, DataPolicy: capability.DataPolicyLocalOnly, Provider: cpuAdapter, Discovery: cpuAdapter}
providers[cpu.Kind] = append(providers[cpu.Kind], cpu)
discoveries["cpu"] = cpu
if lifeboat, enabled, lifeboatErr := newLifeboatProvider(os.Getenv("LOCAL_LIFEBOAT_PROVIDER_URL")); lifeboatErr != nil {
@@ -145,6 +157,25 @@ func refreshProviderDiscoveries(resolver bindingResolver, service discoveryServi
}
}
func loadLocalCPUProviderAPIKey() (string, error) {
if _, set := os.LookupEnv("LOCAL_CPU_PROVIDER_API_KEY"); set {
return "", fmt.Errorf("LOCAL_CPU_PROVIDER_API_KEY direct environment secret is forbidden; use LOCAL_CPU_PROVIDER_API_KEY_FILE")
}
fileName := strings.TrimSpace(os.Getenv("LOCAL_CPU_PROVIDER_API_KEY_FILE"))
if fileName == "" {
return "", nil
}
ref := strings.TrimSpace(os.Getenv("LOCAL_CPU_PROVIDER_CREDENTIAL_REF"))
if ref == "" {
return "", fmt.Errorf("LOCAL_CPU_PROVIDER_CREDENTIAL_REF is required when LOCAL_CPU_PROVIDER_API_KEY_FILE is configured")
}
resolver, err := platformsecrets.NewFileResolver(envOr("SECRET_BACKEND_ROOT", "/run/secrets"))
if err != nil {
return "", err
}
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeProviderAPIKey, Consumer: secretcontracts.RuntimeConsumerCognitive, Path: fileName})
}
func deterministicAdvisory(w http.ResponseWriter, r *http.Request) {
request, ok := decodeAdvisoryRequest(w, r)
if !ok {
+30 -1
View File
@@ -1,6 +1,10 @@
package main
import "testing"
import (
"os"
"path/filepath"
"testing"
)
func TestLifeboatProviderIsOptionalAndLocalAdapterBacked(t *testing.T) {
if _, enabled, err := newLifeboatProvider(""); err != nil || enabled {
@@ -14,3 +18,28 @@ func TestLifeboatProviderIsOptionalAndLocalAdapterBacked(t *testing.T) {
t.Fatalf("unexpected lifeboat registration: %+v", provider)
}
}
func TestLoadLocalCPUProviderAPIKeyUsesProtectedFileReference(t *testing.T) {
t.Setenv("LOCAL_CPU_PROVIDER_API_KEY", "")
_ = os.Unsetenv("LOCAL_CPU_PROVIDER_API_KEY")
root := t.TempDir()
path := filepath.Join(root, "local_cpu_provider_api_key")
if err := os.WriteFile(path, []byte("resolved-key\n"), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("LOCAL_CPU_PROVIDER_API_KEY_FILE", path)
t.Setenv("LOCAL_CPU_PROVIDER_CREDENTIAL_REF", "credential://domovoy/local-cpu/provider-api-key/v1")
t.Setenv("SECRET_BACKEND_ROOT", root)
key, err := loadLocalCPUProviderAPIKey()
if err != nil || key != "resolved-key" {
t.Fatalf("unexpected key resolution result: err=%v", err)
}
}
func TestLoadLocalCPUProviderAPIKeyRejectsDirectEnvironmentSecret(t *testing.T) {
t.Setenv("LOCAL_CPU_PROVIDER_API_KEY", "must-not-be-used")
t.Setenv("LOCAL_CPU_PROVIDER_API_KEY_FILE", "")
if _, err := loadLocalCPUProviderAPIKey(); err == nil {
t.Fatal("expected direct environment secret to be rejected")
}
}
+2
View File
@@ -9,6 +9,7 @@ type SecretPurpose string
const (
SecretPurposeDatabasePassword SecretPurpose = "DATABASE_PASSWORD"
SecretPurposeProviderAPIKey SecretPurpose = "PROVIDER_API_KEY"
)
// RuntimeConsumer identifies a trusted runtime allowed to resolve a credential.
@@ -18,6 +19,7 @@ const (
RuntimeConsumerCore RuntimeConsumer = "core-runtime"
RuntimeConsumerWorkWorker RuntimeConsumer = "agent-worker"
RuntimeConsumerEpistemicsWorker RuntimeConsumer = "reflection-worker"
RuntimeConsumerCognitive RuntimeConsumer = "cognitive-worker"
)
// SecretFileRef is bootstrap delivery metadata for the V1 protected local
+4
View File
@@ -211,11 +211,15 @@ services:
LISTEN_ADDR: ":8083"
CORE_RUNTIME_URL: http://core-runtime:8080
LOCAL_CPU_PROVIDER_URL: http://docker.cin.su:18082/v1
LOCAL_CPU_PROVIDER_API_KEY_FILE: ${LOCAL_CPU_PROVIDER_API_KEY_FILE:-}
LOCAL_CPU_PROVIDER_CREDENTIAL_REF: ${LOCAL_CPU_PROVIDER_CREDENTIAL_REF:-credential://domovoy/local-cpu/provider-api-key/v1}
LOCAL_LIFEBOAT_PROVIDER_URL: http://cognitive-lifeboat:8086/v1
LOCAL_GPU_PROVIDER_URL: http://192.168.220.91:11434
COGNITIVE_OWNER_PRINCIPAL_ID: domovoy-creator-principal
COGNITIVE_ALLOW_CANARY: "true"
depends_on: [core-runtime, cognitive-lifeboat]
volumes:
- assistant_core_secrets:/run/secrets:ro
cognitive-lifeboat:
image: ghcr.io/ggml-org/llama.cpp:server
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000151-agent-worker-status-ui.json",
"task_id": "DEV-000151",
"task_file": "dev/tasks/DEV-000154-fast-live-smoke-evidence.json",
"task_id": "DEV-000154",
"task_revision": 1
}
@@ -0,0 +1,35 @@
{
"task_id": "DEV-000152",
"task_revision": 1,
"architecture_revision": "ADR-0010",
"base_repo_revision": "624c41d4f6dae8c8d341aec09485efbba78bc76f",
"risk_tier": "E",
"intent": "Enable cognitive-worker to authenticate the local OpenAI-compatible CPU gateway through a typed file-backed credential reference without exposing provider key material in environment, source, or logs.",
"requirement_ids": ["AUDIT-WP-003", "AUDIT-WP-201", "CONSTITUTION-1.3.28"],
"allowed_change_scope": {
"modules": ["capability", "cognitive"],
"paths": [
"contracts/secrets/types.go",
"platform/secrets/**",
"adapters/models/openai_compatible_local.go",
"adapters/models/openai_compatible_local_test.go",
"cmd/cognitive-worker/main.go",
"cmd/cognitive-worker/main_test.go",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000152-cognitive-provider-secret-reference.json"
]
},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/devctl/**", ".gitea/**", "deploy/**", "database/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["typed CredentialRef", "protected file-backed secret resolver"], "may_change": ["secret purpose and trusted runtime consumer enum values"]},
"semantic_owner": "capability",
"canonical_state": ["provider credential material remains external to repository and database"],
"state_transitions": ["cognitive-worker sends a bearer credential only when a trusted file-backed reference resolves successfully"],
"side_effects_and_privileges": ["none; adapter receives resolved material from runtime bootstrap and never persists it"],
"persistence_and_migrations": ["no database migration"],
"failure_retry_restart": ["direct secret environment configuration fails closed", "missing reference or unreadable file fails startup", "no configured key preserves existing unauthenticated development behavior"],
"assumptions": ["production operators provision the gateway key into the protected secret volume separately; this task does not copy or reveal it"],
"acceptance_scenarios": ["resolved key becomes Authorization Bearer header", "no key sends no Authorization header", "direct environment secret is rejected", "secret value is absent from logs and repository"],
"required_tests": ["unit", "go test", "go vet", "architecture preflight", "check-diff", "git diff --check", "independent", "exact diff scope", "CI"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_ARCHITECTURE_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000153",
"task_revision": 1,
"architecture_revision": "ADR-0010",
"base_repo_revision": "dbbc54171d9c8a9edaa93e5ff5870d3ec434d799",
"risk_tier": "E",
"intent": "Wire the production cognitive-worker to the existing protected secret volume for optional local CPU gateway authentication without placing credential material in compose environment values.",
"requirement_ids": ["AUDIT-WP-003", "AUDIT-WP-201", "CONSTITUTION-1.3.28"],
"allowed_change_scope": {"modules": ["cognitive"], "paths": ["deploy/docker-compose.prod.yml", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000153-fleet-secret-wiring.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/devctl/**", ".gitea/**", "database/**", "contracts/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 1, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["assistant_core_secrets read-only volume", "optional LOCAL_CPU_PROVIDER_API_KEY_FILE reference"], "may_change": []},
"semantic_owner": "capability",
"canonical_state": ["provider credential material remains outside repository and compose environment"],
"state_transitions": ["when operators provision the referenced file, cognitive-worker resolves it and authenticates the local CPU gateway; when absent, existing recovery behavior remains available"],
"side_effects_and_privileges": ["read-only mount only; no secret value is added to compose"],
"persistence_and_migrations": ["no database migration"],
"failure_retry_restart": ["empty optional file reference preserves current unauthenticated startup", "configured missing credential file fails closed at worker startup"],
"assumptions": ["the gateway credential is provisioned by production operators into assistant_core_secrets; this task does not copy or reveal it"],
"acceptance_scenarios": ["compose renders with empty optional key path", "cognitive-worker receives read-only /run/secrets mount", "no literal provider credential occurs in repository or compose", "CI and production deployment succeed"],
"required_tests": ["go test", "go vet", "docker compose config", "architecture preflight", "check-diff", "git diff --check", "independent", "CI"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_ARCHITECTURE_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000154",
"task_revision": 1,
"architecture_revision": "ADR-0010",
"base_repo_revision": "283f5c044c3bf6739c8ed859412cd736754e3177",
"risk_tier": "E",
"intent": "Record bounded production evidence that the authenticated local CPU provider is available and invokable through the cognitive boundary.",
"requirement_ids": ["AUDIT-WP-003", "AUDIT-WP-201", "WP-601"],
"allowed_change_scope": {"modules": ["capability", "cognitive"], "paths": ["docs/evidence/wp-601-fast-gateway-live-smoke-2026-08-22-v1.json", "docs/evidence/WP-601-FAST-GATEWAY-LIVE-SMOKE-2026-08-22.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000154-fast-live-smoke-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/devctl/**", ".gitea/**", "database/**", "deploy/**", "contracts/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["production cognitive discovery and bounded advisory response evidence"], "may_change": []},
"semantic_owner": "capability",
"canonical_state": ["none; append-only evidence artifact only"],
"state_transitions": ["none; evidence does not activate or promote a binding"],
"side_effects_and_privileges": ["four bounded local-only synthetic advisory requests"],
"persistence_and_migrations": ["no database migration"],
"failure_retry_restart": ["not applicable to evidence artifact"],
"assumptions": ["provider key remains in protected production volume and is not copied to repository or logs"],
"acceptance_scenarios": ["evidence records available API_KEY provider", "four requests complete with expected sentinels", "limitations remain explicit", "no secret material is present"],
"required_tests": ["JSON validation", "go test", "go vet", "architecture preflight", "check-diff", "git diff --check", "independent", "CI"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_ARCHITECTURE_GAP"]
}
@@ -0,0 +1,14 @@
# WP-601 FAST gateway live smoke
Observed `2026-08-22T14:41:47Z` on production revision `283f5c044c3bf6739c8ed859412cd736754e3177`.
The protected provider reference was enabled in production without retaining
credential material in the repository or evidence. Cognitive discovery reported
`local-cpu-primary` as `AVAILABLE` with `AuthMode=API_KEY` and observed models
`Qwen3.6-27B-UD-Q4_K_XL` and `dspark-DeepSeek-V4-Flash-0731-Q8_0`.
Four bounded local-only advisory requests crossed the cognitive boundary and
completed with the expected sentinel response: 4/4, zero failures. This proves
availability and invocation of FAST; it does not promote a binding or claim that
MEDIUM/HEAVY are available. Existing Lifeboat qualification remains the fallback
evidence.
@@ -0,0 +1,31 @@
{
"artifact_id": "wp-601-fast-gateway-live-smoke-2026-08-22-v1",
"observed_at": "2026-08-22T14:41:47.1155793Z",
"production_revision": "283f5c044c3bf6739c8ed859412cd736754e3177",
"endpoint": "assistant.cin.su",
"runtime_path": "creator-web -> cognitive-worker -> local-cpu-primary -> llm-local-gateway",
"credential_material": "not retained; protected file reference only",
"provider": {
"provider_instance_id": "local-cpu-primary",
"provider_kind": "openai-compatible-chat-v1",
"health": "AVAILABLE",
"auth_mode": "API_KEY",
"credential_ref": "credential://domovoy/local-cpu/provider-api-key/v1",
"models_observed": [
"Qwen3.6-27B-UD-Q4_K_XL",
"dspark-DeepSeek-V4-Flash-0731-Q8_0"
]
},
"cases": [
{"request_id": "live-fast-smoke-20260822", "expected": "FAST_OK", "actual": "FAST_OK", "finish_reason": "COMPLETED", "input_units": 36, "output_units": 3},
{"request_id": "live-fast-case-1-20260822", "expected": "CASE_OK_1", "actual": "CASE_OK_1", "finish_reason": "COMPLETED", "input_units": 38, "output_units": 5},
{"request_id": "live-fast-case-2-20260822", "expected": "CASE_OK_2", "actual": "CASE_OK_2", "finish_reason": "COMPLETED", "input_units": 38, "output_units": 5},
{"request_id": "live-fast-case-3-20260822", "expected": "CASE_OK_3", "actual": "CASE_OK_3", "finish_reason": "COMPLETED", "input_units": 38, "output_units": 5}
],
"summary": {"cases": 4, "completed": 4, "failed": 0, "qualification": "LIVE_SMOKE_SUPPORTED"},
"limitations": [
"This is availability and bounded invocation evidence, not a binding promotion.",
"Medium and heavy fleet members remain unconfigured/unavailable.",
"Fallback qualification remains covered by the existing Lifeboat evidence."
]
}