Implement governance sentinel v2 #97
@@ -15,6 +15,8 @@ jobs:
|
||||
go-version: '1.26.6'
|
||||
- name: Active task preflight
|
||||
run: go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json
|
||||
- name: Independent governance verifier
|
||||
run: go run ./cmd/devctl/independent
|
||||
- name: Exact diff scope
|
||||
run: go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json
|
||||
- name: Format check
|
||||
@@ -25,3 +27,5 @@ jobs:
|
||||
run: go test ./...
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
- name: Diff whitespace check
|
||||
run: git diff --check
|
||||
|
||||
@@ -2,7 +2,9 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -10,6 +12,12 @@ import (
|
||||
|
||||
type WorkRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
var (
|
||||
ErrDispatchIntentConflict = errors.New("dispatch intent idempotency conflict")
|
||||
ErrAgentLeaseUnavailable = errors.New("agent lease unavailable")
|
||||
ErrAgentCallbackRejected = errors.New("agent callback rejected")
|
||||
)
|
||||
|
||||
func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, error) {
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
@@ -39,6 +47,61 @@ func (r *WorkRepository) Load(ctx context.Context, id work.TaskID) (work.TaskTar
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) SaveDispatchIntent(ctx context.Context, intent work.AgentDispatchIntent) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
_, err = tx.Exec(ctx, `INSERT INTO work.agent_dispatch_intents(dispatch_id,idempotency_key,delegation_id,run_id,implementation_id,implementation_revision,source_revision,worktree_id,verification_plan_ref,created_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT(idempotency_key) DO NOTHING`, intent.DispatchID, intent.IdempotencyKey, intent.DelegationID, intent.RunID, intent.ImplementationID, intent.ImplementationRevision, intent.SourceRevision, intent.WorktreeID, intent.VerificationPlanRef, intent.CreatedAtUnixMillis)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save dispatch intent: %w", err)
|
||||
}
|
||||
var existing work.AgentDispatchIntent
|
||||
err = tx.QueryRow(ctx, `SELECT dispatch_id,idempotency_key,delegation_id,run_id,implementation_id,implementation_revision,source_revision,worktree_id,verification_plan_ref,created_at_unix_millis FROM work.agent_dispatch_intents WHERE idempotency_key=$1`, intent.IdempotencyKey).Scan(&existing.DispatchID, &existing.IdempotencyKey, &existing.DelegationID, &existing.RunID, &existing.ImplementationID, &existing.ImplementationRevision, &existing.SourceRevision, &existing.WorktreeID, &existing.VerificationPlanRef, &existing.CreatedAtUnixMillis)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read dispatch intent: %w", err)
|
||||
}
|
||||
if existing != intent {
|
||||
return ErrDispatchIntentConflict
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *WorkRepository) SaveAgentLease(ctx context.Context, lease work.AgentLease) error {
|
||||
err := r.pool.QueryRow(ctx, `INSERT INTO work.agent_leases(lease_id,run_id,worker_id,fencing_token,state,acquired_at_unix_millis,expires_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7) ON CONFLICT(run_id) DO UPDATE SET lease_id=EXCLUDED.lease_id,worker_id=EXCLUDED.worker_id,fencing_token=EXCLUDED.fencing_token,state=EXCLUDED.state,acquired_at_unix_millis=EXCLUDED.acquired_at_unix_millis,expires_at_unix_millis=EXCLUDED.expires_at_unix_millis WHERE work.agent_leases.state='EXPIRED' OR work.agent_leases.expires_at_unix_millis <= EXCLUDED.acquired_at_unix_millis RETURNING run_id`, lease.ID, lease.RunID, lease.WorkerID, lease.FencingToken, lease.State, lease.AcquiredAt.UnixMilli(), lease.ExpiresAt.UnixMilli()).Scan(new(string))
|
||||
if err != nil {
|
||||
return ErrAgentLeaseUnavailable
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) RenewAgentLease(ctx context.Context, lease work.AgentLease, expiresAt time.Time) error {
|
||||
command, err := r.pool.Exec(ctx, `UPDATE work.agent_leases SET expires_at_unix_millis=$1 WHERE run_id=$2 AND worker_id=$3 AND fencing_token=$4 AND state='ACTIVE' AND expires_at_unix_millis > $5`, expiresAt.UnixMilli(), lease.RunID, lease.WorkerID, lease.FencingToken, time.Now().UnixMilli())
|
||||
if err != nil || command.RowsAffected() != 1 {
|
||||
return ErrAgentLeaseUnavailable
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) AcceptAgentCallback(ctx context.Context, callback work.AgentCallback) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
var runID string
|
||||
err = tx.QueryRow(ctx, `SELECT run_id FROM work.agent_leases WHERE run_id=$1 AND worker_id=$2 AND fencing_token=$3 AND state='ACTIVE' AND expires_at_unix_millis > $4 FOR UPDATE`, callback.RunID, callback.WorkerID, callback.FencingToken, callback.ObservedAt.UnixMilli()).Scan(&runID)
|
||||
if err != nil {
|
||||
return ErrAgentCallbackRejected
|
||||
}
|
||||
command, err := tx.Exec(ctx, `INSERT INTO work.agent_callback_heads(run_id,last_sequence,updated_at_unix_millis) VALUES($1,$2,$3) ON CONFLICT(run_id) DO UPDATE SET last_sequence=EXCLUDED.last_sequence,updated_at_unix_millis=EXCLUDED.updated_at_unix_millis WHERE work.agent_callback_heads.last_sequence < EXCLUDED.last_sequence`, runID, callback.Sequence, callback.ObservedAt.UnixMilli())
|
||||
if err != nil || command.RowsAffected() != 1 {
|
||||
return ErrAgentCallbackRejected
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *WorkRepository) SavePlanningResult(ctx context.Context, result work.PlanningResult) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -65,7 +65,12 @@
|
||||
"WorkPlan",
|
||||
"PlanStep",
|
||||
"CapabilityRequirement",
|
||||
"DelegationCandidate"
|
||||
"DelegationCandidate",
|
||||
"Delegation",
|
||||
"AgentRun",
|
||||
"AgentCheckpoint",
|
||||
"AgentResult",
|
||||
"DelegationVerification"
|
||||
],
|
||||
"database_schema": "work",
|
||||
"boundary_class": "remote_capable",
|
||||
|
||||
@@ -25,17 +25,17 @@ modules:
|
||||
|
||||
situation:
|
||||
id: mod.situation
|
||||
owns: [Situation, FocusBinding]
|
||||
owns: [Situation, FocusBinding, ProjectRegistry, ActiveProjectFocus]
|
||||
database_schema: situation
|
||||
boundary_class: remote_capable
|
||||
may_depend_on: [contracts.identity, contracts.work, contracts.capability]
|
||||
may_depend_on: [contracts.identity, contracts.authority, contracts.work, contracts.capability]
|
||||
|
||||
work:
|
||||
id: mod.work
|
||||
owns: [Goal, Task, Decision]
|
||||
owns: [Goal, HelpPath, GoalLifecycle, Task, Decision, PlanCandidate, WorkPlan, PlanStep, CapabilityRequirement, DelegationCandidate, Delegation, AgentRun, AgentCheckpoint, AgentResult, DelegationVerification]
|
||||
database_schema: work
|
||||
boundary_class: remote_capable
|
||||
may_depend_on: [contracts.identity, contracts.authority, platform.events]
|
||||
may_depend_on: [contracts.identity, contracts.authority, contracts.epistemics, contracts.capability, platform.ids, platform.events]
|
||||
|
||||
epistemics:
|
||||
id: mod.epistemics
|
||||
@@ -53,7 +53,7 @@ modules:
|
||||
|
||||
capability:
|
||||
id: mod.capability
|
||||
owns: [Capability, CapabilityBinding, CapabilityHealth]
|
||||
owns: [Capability, CapabilityBinding, CapabilityHealth, CapabilityImplementation, CapabilityResolution, CapabilitySelection, CapabilityGap, AcquisitionStrategyCandidate, CapabilityUsageObservation, SkillProfile, SkillEvidence, FastPathDecision]
|
||||
database_schema: capability
|
||||
boundary_class: remote_capable
|
||||
may_depend_on: [contracts.identity, contracts.authority, contracts.artifact]
|
||||
@@ -81,11 +81,12 @@ platform:
|
||||
- platform.inbox
|
||||
- platform.telemetry
|
||||
- platform.errors
|
||||
- platform.secrets
|
||||
rule: "platform owns mechanisms, never domain meaning"
|
||||
|
||||
runtime_topology_v1:
|
||||
core_runtime:
|
||||
modules: [identity, situation, work, epistemics, capability, artifact]
|
||||
modules: [identity, situation, work, epistemics, capability, artifact, interaction]
|
||||
continuity_runtime:
|
||||
modules: [continuity]
|
||||
commit_runtime:
|
||||
@@ -107,6 +108,7 @@ storage_topology_v1:
|
||||
capability: capability_role
|
||||
commit: commit_role
|
||||
artifact: artifact_role
|
||||
interaction: interaction_role
|
||||
cross_owner_fk: forbidden_by_default
|
||||
cross_owner_write: forbidden
|
||||
cross_owner_application_join: forbidden
|
||||
@@ -142,6 +144,10 @@ protected_paths:
|
||||
- architecture/architecture-registry.yaml
|
||||
- architecture/adrs/**
|
||||
- ci/architecture/**
|
||||
- .gitea/workflows/ci.yml
|
||||
- cmd/devctl/**
|
||||
- dev/ACTIVE_TASK.json
|
||||
- ci/architecture/**
|
||||
- security/**
|
||||
|
||||
default_feature_task_authority:
|
||||
|
||||
@@ -27,7 +27,7 @@ modules:
|
||||
|
||||
work:
|
||||
id: mod.work
|
||||
owns: [Goal, HelpPath, GoalLifecycle, Task, Decision, PlanCandidate, WorkPlan, PlanStep, CapabilityRequirement, DelegationCandidate]
|
||||
owns: [Goal, HelpPath, GoalLifecycle, Task, Decision, PlanCandidate, WorkPlan, PlanStep, CapabilityRequirement, DelegationCandidate, Delegation, AgentRun, AgentCheckpoint, AgentResult, DelegationVerification]
|
||||
database_schema: work
|
||||
boundary_class: remote_capable
|
||||
may_depend_on: [contracts.identity, contracts.authority, contracts.epistemics, contracts.capability, platform.ids, platform.events]
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/adapters/postgres"
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/application"
|
||||
"git.cin.su/m/Assistant/modules/work/ports"
|
||||
)
|
||||
|
||||
type memoryStore struct{ writes int }
|
||||
|
||||
func (s *memoryStore) SaveDispatchIntent(context.Context, work.AgentDispatchIntent) error {
|
||||
s.writes++
|
||||
return nil
|
||||
}
|
||||
func (s *memoryStore) SaveAgentLease(context.Context, work.AgentLease) error { s.writes++; return nil }
|
||||
func (s *memoryStore) AcceptAgentCallback(context.Context, work.AgentCallback) error {
|
||||
s.writes++
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
enabled := flag.Bool("enable-canary", false, "explicitly enable the manual canary")
|
||||
implementation := flag.String("implementation", "fake-worker", "allowlisted deterministic implementation")
|
||||
postgresMode := flag.Bool("postgres", false, "use the Work PostgreSQL repository instead of in-memory storage")
|
||||
flag.Parse()
|
||||
|
||||
now := time.UnixMilli(1_700_000_000_000)
|
||||
intent := work.AgentDispatchIntent{DispatchID: "manual-dispatch-1", IdempotencyKey: "manual-idempotency-1", DelegationID: "manual-delegation-1", RunID: "manual-run-1", ImplementationID: *implementation, ImplementationRevision: 1, SourceRevision: "manual-source-1", WorktreeID: "manual-worktree-1", VerificationPlanRef: "manual-verification-1", CreatedAtUnixMillis: now.UnixMilli()}
|
||||
var store ports.ExecutionStore = &memoryStore{}
|
||||
var closeStore func()
|
||||
if *postgresMode {
|
||||
repository, err := postgres.OpenWork(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("WORK_POSTGRES_USER"), Password: os.Getenv("WORK_POSTGRES_PASSWORD")})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
store = repository
|
||||
closeStore = repository.Close
|
||||
}
|
||||
if closeStore != nil {
|
||||
defer closeStore()
|
||||
}
|
||||
result, err := application.ExecuteCanaryDryRun(context.Background(), store, application.CanaryConfig{Enabled: *enabled, ImplementationID: "fake-worker", MaxDispatches: 1}, 0, intent, "manual-worker", 1, now, 15*time.Minute)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
output := struct {
|
||||
DispatchID string `json:"dispatch_id"`
|
||||
RunID string `json:"run_id"`
|
||||
CallbackCount int `json:"callback_count"`
|
||||
StoreWrites int `json:"store_writes"`
|
||||
}{result.Intent.DispatchID, string(result.Intent.RunID), result.CallbackCount(), 0}
|
||||
if memory, ok := store.(*memoryStore); ok {
|
||||
output.StoreWrites = memory.writes
|
||||
}
|
||||
if err := json.NewEncoder(os.Stdout).Encode(output); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -71,6 +72,14 @@ func main() {
|
||||
}
|
||||
gateway := cognitiveGateway{resolver: resolver, providers: providers, builtinLocal: builtinLocal, externalRecovery: externalRecovery, now: time.Now, allowCanary: os.Getenv("COGNITIVE_ALLOW_CANARY") == "true", canaryMaxOutputBytes: 4096}
|
||||
mux.Handle("/healthz", health.Handler{Runtime: "cognitive-worker", SemanticGeneration: 1, ArchitectureGeneration: 1})
|
||||
readinessClient := &http.Client{Timeout: 2 * time.Second}
|
||||
lifeboatURL := strings.TrimSuffix(strings.TrimRight(envOr("LOCAL_LIFEBOAT_PROVIDER_URL", "http://cognitive-lifeboat:8086/v1"), "/"), "/v1") + "/health"
|
||||
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "cognitive-worker", Probes: []health.Probe{
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("CORE_RUNTIME_URL", "http://core-runtime:8080"), "/")+"/healthz"),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("LOCAL_CPU_PROVIDER_URL", "http://docker.cin.su:18082/v1"), "/")+"/models"),
|
||||
health.HTTPProbe(readinessClient, lifeboatURL),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("LOCAL_GPU_PROVIDER_URL", "http://192.168.220.91:11434"), "/")+"/api/tags"),
|
||||
}})
|
||||
mux.HandleFunc("POST /v1/proposals/deterministic", deterministicProposal)
|
||||
mux.HandleFunc("POST /v1/advisory-policy", gateway.policyHandler)
|
||||
mux.HandleFunc("POST /v1/advisories", gateway.advisoryHandler)
|
||||
|
||||
@@ -84,6 +84,13 @@ func main() {
|
||||
dispatcher := operationDispatcher{continuityExecutor: continuityExecutor, continuityReconciler: continuityExecutor, bindings: creatorCore, secretRef: notificationSecretRef, secret: notificationSecret, client: creatorHTTPClient}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/healthz", health.Handler{Runtime: "commit-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1})
|
||||
readinessClient := &http.Client{Timeout: 2 * time.Second}
|
||||
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "commit-runtime", Probes: []health.Probe{
|
||||
health.TCPProbe("commit-postgres", envOr("POSTGRES_HOST", "postgres"), envOr("POSTGRES_PORT", "5432")),
|
||||
health.TCPProbe("authority-postgres", envOr("POSTGRES_HOST", "postgres"), envOr("POSTGRES_PORT", "5432")),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(coreURL, "/")+"/healthz"),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), "/")+"/healthz"),
|
||||
}})
|
||||
registerExecutionRoutes(mux, repository, dispatcher, dispatcher, executorToken)
|
||||
registerCreatorCommandRoutes(mux, creatorCommandHandler{
|
||||
core: creatorCore, continuity: creatorContinuity, authority: authorityRepository, repository: repository,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/adapters/postgres"
|
||||
@@ -31,6 +32,12 @@ func main() {
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/healthz", health.Handler{Runtime: "continuity-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1})
|
||||
readinessClient := &http.Client{Timeout: 2 * time.Second}
|
||||
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "continuity-runtime", Probes: []health.Probe{
|
||||
health.TCPProbe("postgres", envOr("POSTGRES_HOST", "postgres"), envOr("POSTGRES_PORT", "5432")),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("CORE_RUNTIME_URL", "http://core-runtime:8080"), "/")+"/healthz"),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("COMMIT_RUNTIME_URL", "http://commit-runtime:8082"), "/")+"/healthz"),
|
||||
}})
|
||||
executorToken := os.Getenv("COMMIT_EXECUTOR_TOKEN")
|
||||
if executorToken == "" {
|
||||
log.Fatal("COMMIT_EXECUTOR_TOKEN is required")
|
||||
|
||||
@@ -135,6 +135,12 @@ func main() {
|
||||
return situationdomain.NewResolver(bindings).Resolve(runtime)
|
||||
}
|
||||
mux.Handle("/healthz", health.Handler{Runtime: "core-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1})
|
||||
readinessClient := &http.Client{Timeout: 2 * time.Second}
|
||||
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "core-runtime", Probes: []health.Probe{
|
||||
health.TCPProbe("postgres", envOr("POSTGRES_HOST", "postgres"), envOr("POSTGRES_PORT", "5432")),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), "/")+"/healthz"),
|
||||
health.HTTPProbe(readinessClient, strings.TrimRight(envOr("COGNITIVE_RUNTIME_URL", "http://cognitive-worker:8083"), "/")+"/healthz"),
|
||||
}})
|
||||
mux.HandleFunc("POST /v1/creator/sessions", creatorSessionHandler(bootstrapToken, creatorUsername, creatorPassword, sessionKey, time.Now))
|
||||
mux.HandleFunc("GET /v1/creator/sessions/current", func(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := authenticateCreator(w, r)
|
||||
|
||||
@@ -378,7 +378,7 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
|
||||
cognition = "unavailable"
|
||||
requestID, idErr := ids.New()
|
||||
if idErr == nil {
|
||||
trusted := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: "domovoy-global-scope", Message: browserRequest.Message, Context: []cognitive.ContextExcerpt{}, Locale: "ru-RU", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, EgressAcknowledged: false, Meaning: cognitive.TaskMeaning{SpeechAct: string(saved.SpeechAct), CapabilityKind: saved.CapabilityKind, CapabilityHints: saved.CapabilityHints, RequestedOutcome: saved.RequestedOutcome, InformationRequestKind: saved.InformationRequestKind, ConfidenceMillis: saved.InterpretationConfidenceMillis}}
|
||||
trusted := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: "domovoy-global-scope", Message: browserRequest.Message, Context: localChatContext(saved), Locale: "ru-RU", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, EgressAcknowledged: false, Meaning: cognitive.TaskMeaning{SpeechAct: string(saved.SpeechAct), CapabilityKind: saved.CapabilityKind, CapabilityHints: saved.CapabilityHints, RequestedOutcome: saved.RequestedOutcome, InformationRequestKind: saved.InformationRequestKind, ConfidenceMillis: saved.InterpretationConfidenceMillis}}
|
||||
payload, err = json.Marshal(trusted)
|
||||
if err == nil {
|
||||
var advisory cognitive.GatewayAdvisoryResponse
|
||||
@@ -415,6 +415,30 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func localChatContext(saved interaction.ChatMessageResponse) []cognitive.ContextExcerpt {
|
||||
if strings.TrimSpace(saved.ActiveProjectName) == "" {
|
||||
return []cognitive.ContextExcerpt{}
|
||||
}
|
||||
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{
|
||||
DataPolicy: capability.DataPolicyLocalOnly,
|
||||
MaxBytes: 2048,
|
||||
Candidates: []cognitive.ContextCandidate{{
|
||||
Excerpt: cognitive.ContextExcerpt{
|
||||
ID: cognitive.ContextExcerptID("active-project-focus"),
|
||||
SourceKind: "situation-active-project",
|
||||
SourceID: strings.TrimSpace(saved.ActiveProjectName),
|
||||
Sensitivity: cognitive.SensitivityPrivateAllowed,
|
||||
Text: "Текущий проект: " + strings.TrimSpace(saved.ActiveProjectName),
|
||||
},
|
||||
Relevance: 100,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return []cognitive.ContextExcerpt{}
|
||||
}
|
||||
return bundle.Excerpts
|
||||
}
|
||||
|
||||
func asksAttention(message string) bool {
|
||||
lower := strings.ToLower(message)
|
||||
return strings.Contains(lower, "требует моего внимания") || strings.Contains(lower, "требует внимания") || strings.Contains(lower, "что сейчас важно")
|
||||
|
||||
@@ -10,8 +10,19 @@ import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
)
|
||||
|
||||
func TestLocalChatContextUsesOnlyActiveProject(t *testing.T) {
|
||||
context := localChatContext(interaction.ChatMessageResponse{ActiveProjectName: "Домовой"})
|
||||
if len(context) != 1 || context[0].SourceKind != "situation-active-project" || context[0].Text != "Текущий проект: Домовой" {
|
||||
t.Fatalf("unexpected local context: %#v", context)
|
||||
}
|
||||
if empty := localChatContext(interaction.ChatMessageResponse{}); len(empty) != 0 {
|
||||
t.Fatalf("expected empty context without active project: %#v", empty)
|
||||
}
|
||||
}
|
||||
|
||||
const legacyExternalMode = "legacy-external-test-mode"
|
||||
|
||||
func newHandlerWithAdvisoryMode(coreURL, continuityURL, commitURL string, client *http.Client, mode string, cognitiveURLs ...string) (http.Handler, error) {
|
||||
|
||||
@@ -166,7 +166,7 @@ func loadSelectedTask(root string, selection taskSelection) (TaskPacket, string,
|
||||
|
||||
func loadTaskPacket(root, taskPath string) (TaskPacket, error) {
|
||||
var taskPacket TaskPacket
|
||||
if err := decodeJSONFile(filepath.Join(root, filepath.FromSlash(taskPath)), &taskPacket, false); err != nil {
|
||||
if err := decodeJSONFile(filepath.Join(root, filepath.FromSlash(taskPath)), &taskPacket, true); err != nil {
|
||||
return TaskPacket{}, fmt.Errorf("task packet: %w", err)
|
||||
}
|
||||
return taskPacket, nil
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var required = []string{
|
||||
"task_id", "task_revision", "architecture_revision", "base_repo_revision", "risk_tier", "intent",
|
||||
"requirement_ids", "allowed_change_scope", "forbidden_change_scope", "allowed_architecture_delta",
|
||||
"contracts", "semantic_owner", "canonical_state", "state_transitions", "side_effects_and_privileges",
|
||||
"persistence_and_migrations", "failure_retry_restart", "assumptions", "acceptance_scenarios",
|
||||
"required_tests", "terminal_gap_states",
|
||||
}
|
||||
|
||||
func main() {
|
||||
activePath := filepath.FromSlash("dev/ACTIVE_TASK.json")
|
||||
var active struct {
|
||||
TaskFile string `json:"task_file"`
|
||||
TaskID string `json:"task_id"`
|
||||
TaskRevision int `json:"task_revision"`
|
||||
}
|
||||
if err := decode(activePath, &active); err != nil {
|
||||
fail("active pointer: %v", err)
|
||||
}
|
||||
if active.TaskFile == "" || active.TaskID == "" || active.TaskRevision < 1 {
|
||||
fail("active pointer is incomplete")
|
||||
}
|
||||
var raw map[string]json.RawMessage
|
||||
if err := decode(filepath.FromSlash(active.TaskFile), &raw); err != nil {
|
||||
fail("task packet: %v", err)
|
||||
}
|
||||
for _, key := range required {
|
||||
if _, ok := raw[key]; !ok {
|
||||
fail("task packet missing required field %q", key)
|
||||
}
|
||||
}
|
||||
var packet struct {
|
||||
TaskID string `json:"task_id"`
|
||||
TaskRevision int `json:"task_revision"`
|
||||
}
|
||||
if err := decode(filepath.FromSlash(active.TaskFile), &packet); err != nil {
|
||||
fail("task identity: %v", err)
|
||||
}
|
||||
if packet.TaskID != active.TaskID || packet.TaskRevision != active.TaskRevision {
|
||||
fail("active pointer and packet identity differ")
|
||||
}
|
||||
fmt.Printf("independent governance verification: OK task=%s revision=%d\n", packet.TaskID, packet.TaskRevision)
|
||||
}
|
||||
|
||||
func decode(path string, target any) error {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
|
||||
func fail(format string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, "independent verifier: "+format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
+32
-11
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
@@ -24,19 +23,41 @@ type Registry struct {
|
||||
BoundaryClass string `json:"boundary_class"`
|
||||
MayDependOn []string `json:"may_depend_on"`
|
||||
Forbidden []string `json:"forbidden"`
|
||||
Privileged bool `json:"privileged"`
|
||||
} `json:"modules"`
|
||||
ProtectedPaths []string `json:"protected_paths"`
|
||||
}
|
||||
|
||||
type TaskPacket struct {
|
||||
TaskID string `json:"task_id"`
|
||||
TaskRevision int `json:"task_revision"`
|
||||
BaseRepoRevision string `json:"base_repo_revision"`
|
||||
RiskTier string `json:"risk_tier"`
|
||||
Intent string `json:"intent"`
|
||||
AllowedChangeScope struct {
|
||||
Paths []string `json:"paths"`
|
||||
TaskID string `json:"task_id"`
|
||||
TaskRevision int `json:"task_revision"`
|
||||
ArchitectureRevision string `json:"architecture_revision"`
|
||||
BaseRepoRevision string `json:"base_repo_revision"`
|
||||
RiskTier string `json:"risk_tier"`
|
||||
Intent string `json:"intent"`
|
||||
RequirementIDs []string `json:"requirement_ids"`
|
||||
AllowedChangeScope struct {
|
||||
Modules []string `json:"modules"`
|
||||
Paths []string `json:"paths"`
|
||||
} `json:"allowed_change_scope"`
|
||||
ForbiddenChangeScope struct {
|
||||
Paths []string `json:"paths"`
|
||||
} `json:"forbidden_change_scope"`
|
||||
AllowedArchitectureDelta map[string]int `json:"allowed_architecture_delta"`
|
||||
Contracts struct {
|
||||
Consume []string `json:"consume"`
|
||||
MayChange []string `json:"may_change"`
|
||||
} `json:"contracts"`
|
||||
SemanticOwner string `json:"semantic_owner"`
|
||||
CanonicalState []string `json:"canonical_state"`
|
||||
StateTransitions []string `json:"state_transitions"`
|
||||
SideEffectsAndPrivileges []string `json:"side_effects_and_privileges"`
|
||||
PersistenceAndMigrations []string `json:"persistence_and_migrations"`
|
||||
FailureRetryRestart []string `json:"failure_retry_restart"`
|
||||
Assumptions []string `json:"assumptions"`
|
||||
AcceptanceScenarios []string `json:"acceptance_scenarios"`
|
||||
RequiredTests []string `json:"required_tests"`
|
||||
TerminalGapStates []string `json:"terminal_gap_states"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -207,12 +228,12 @@ func preflight(args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskPacket, _, _, err := loadSelectedTask(root, selection)
|
||||
taskPacket, taskPath, _, err := loadSelectedTask(root, selection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if taskPacket.TaskID == "" || taskPacket.Intent == "" || taskPacket.RiskTier == "" {
|
||||
return errors.New("task packet missing task_id, intent, or risk_tier")
|
||||
if err := validateTaskPacket(root, taskPacket, taskPath); err != nil {
|
||||
return err
|
||||
}
|
||||
registry, err := loadRegistry()
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var requiredPacketKeys = []string{
|
||||
"task_id", "task_revision", "architecture_revision", "base_repo_revision", "risk_tier", "intent",
|
||||
"requirement_ids", "allowed_change_scope", "forbidden_change_scope", "allowed_architecture_delta",
|
||||
"contracts", "semantic_owner", "canonical_state", "state_transitions", "side_effects_and_privileges",
|
||||
"persistence_and_migrations", "failure_retry_restart", "assumptions", "acceptance_scenarios",
|
||||
"required_tests", "terminal_gap_states",
|
||||
}
|
||||
|
||||
var requiredDeltaKeys = []string{
|
||||
"new_cross_module_dependencies", "new_canonical_owners", "new_privileges",
|
||||
"new_third_party_dependencies", "new_database_owner_roles",
|
||||
}
|
||||
|
||||
func validateTaskPacket(root string, task TaskPacket, taskPath string) error {
|
||||
if task.TaskID == "" || task.TaskRevision < 1 || task.ArchitectureRevision == "" || task.BaseRepoRevision == "" || task.RiskTier == "" || task.Intent == "" {
|
||||
return fmt.Errorf("task %s is incomplete: identity, revision, architecture_revision, base_repo_revision, risk_tier and intent are required", task.TaskID)
|
||||
}
|
||||
if taskPath == "" || !matchesAny(taskPath, mustCompile([]string{"dev/tasks/*.json"})) {
|
||||
return fmt.Errorf("task %s must be selected from dev/tasks", task.TaskID)
|
||||
}
|
||||
if task.RiskTier < "A" || task.RiskTier > "E" {
|
||||
return fmt.Errorf("task %s has invalid risk_tier %q", task.TaskID, task.RiskTier)
|
||||
}
|
||||
if len(task.RequirementIDs) == 0 || len(task.AcceptanceScenarios) == 0 || len(task.RequiredTests) == 0 || len(task.TerminalGapStates) == 0 {
|
||||
return fmt.Errorf("task %s must declare requirements, acceptance_scenarios, required_tests and terminal_gap_states", task.TaskID)
|
||||
}
|
||||
if len(task.AllowedChangeScope.Paths) == 0 {
|
||||
return fmt.Errorf("task %s must declare allowed_change_scope.paths", task.TaskID)
|
||||
}
|
||||
if err := validateScopeOverlap(task.AllowedChangeScope.Paths, task.ForbiddenChangeScope.Paths); err != nil {
|
||||
return fmt.Errorf("task %s scope: %w", task.TaskID, err)
|
||||
}
|
||||
if err := validateDelta(task.AllowedArchitectureDelta); err != nil {
|
||||
return fmt.Errorf("task %s architecture delta: %w", task.TaskID, err)
|
||||
}
|
||||
if err := validateRequiredTests(task.RequiredTests); err != nil {
|
||||
return fmt.Errorf("task %s required_tests: %w", task.TaskID, err)
|
||||
}
|
||||
if err := validateArchitectureRevision(root, task.ArchitectureRevision); err != nil {
|
||||
return fmt.Errorf("task %s architecture_revision: %w", task.TaskID, err)
|
||||
}
|
||||
if _, err := resolveBaseCommit(root, task.BaseRepoRevision); err != nil {
|
||||
return fmt.Errorf("task %s base_repo_revision: %w", task.TaskID, err)
|
||||
}
|
||||
if err := validateObservedDelta(root, task); err != nil {
|
||||
return fmt.Errorf("task %s observed architecture delta: %w", task.TaskID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRequiredTests(tests []string) error {
|
||||
joined := strings.Join(tests, "\n")
|
||||
for _, required := range []string{"go test", "go vet", "preflight", "check-diff", "git diff --check", "independent"} {
|
||||
if !strings.Contains(joined, required) {
|
||||
return fmt.Errorf("missing required governance evidence %q", required)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateObservedDelta(root string, task TaskPacket) error {
|
||||
changed, err := changedPaths(root, task.BaseRepoRevision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateRegistryDelta(root, task); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, changedPath := range changed {
|
||||
if (strings.HasPrefix(changedPath, "security/") || strings.HasPrefix(changedPath, "deploy/")) && task.AllowedArchitectureDelta["new_privileges"] == 0 {
|
||||
return fmt.Errorf("privileged surface changed with new_privileges=0: %s", changedPath)
|
||||
}
|
||||
if strings.HasPrefix(changedPath, "database/") && task.AllowedArchitectureDelta["new_database_owner_roles"] == 0 {
|
||||
return fmt.Errorf("database surface changed with new_database_owner_roles=0: %s", changedPath)
|
||||
}
|
||||
if (changedPath == "go.mod" || changedPath == "go.sum") && task.AllowedArchitectureDelta["new_third_party_dependencies"] == 0 {
|
||||
return fmt.Errorf("dependency manifest changed with new_third_party_dependencies=0: %s", changedPath)
|
||||
}
|
||||
if strings.HasPrefix(changedPath, "contracts/") && len(task.Contracts.MayChange) == 0 {
|
||||
return fmt.Errorf("contract surface changed but contracts.may_change is empty: %s", changedPath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRegistryDelta(root string, task TaskPacket) error {
|
||||
baseBytes, err := runGit(root, "show", task.BaseRepoRevision+":architecture/architecture-registry.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("read base architecture registry: %w", err)
|
||||
}
|
||||
currentBytes, err := os.ReadFile(filepath.Join(root, "architecture", "architecture-registry.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var base, current Registry
|
||||
if err := json.Unmarshal(baseBytes, &base); err != nil {
|
||||
return fmt.Errorf("parse base architecture registry: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(currentBytes, ¤t); err != nil {
|
||||
return fmt.Errorf("parse current architecture registry: %w", err)
|
||||
}
|
||||
if task.AllowedArchitectureDelta["new_canonical_owners"] == 0 {
|
||||
for name := range current.Modules {
|
||||
if _, ok := base.Modules[name]; !ok {
|
||||
return fmt.Errorf("new canonical module owner %q with new_canonical_owners=0", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if task.AllowedArchitectureDelta["new_cross_module_dependencies"] == 0 {
|
||||
for name, module := range current.Modules {
|
||||
previous, ok := base.Modules[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
old := map[string]bool{}
|
||||
for _, dep := range previous.MayDependOn {
|
||||
old[dep] = true
|
||||
}
|
||||
for _, dep := range module.MayDependOn {
|
||||
if !old[dep] {
|
||||
return fmt.Errorf("new dependency %q in module %q with new_cross_module_dependencies=0", dep, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if task.AllowedArchitectureDelta["new_privileges"] == 0 {
|
||||
for name, module := range current.Modules {
|
||||
if previous, ok := base.Modules[name]; ok && module.Privileged && !previous.Privileged {
|
||||
return fmt.Errorf("new privilege in module %q with new_privileges=0", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDelta(delta map[string]int) error {
|
||||
if len(delta) != len(requiredDeltaKeys) {
|
||||
return fmt.Errorf("must contain exactly %s", strings.Join(requiredDeltaKeys, ", "))
|
||||
}
|
||||
for _, key := range requiredDeltaKeys {
|
||||
value, ok := delta[key]
|
||||
if !ok || value < 0 {
|
||||
return fmt.Errorf("%s must be a non-negative integer", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArchitectureRevision(root, revision string) error {
|
||||
if strings.HasPrefix(revision, "ADR-") {
|
||||
matches, err := filepath.Glob(filepath.Join(root, "architecture", "adrs", revision+"-*.md"))
|
||||
if err != nil || len(matches) != 1 {
|
||||
return fmt.Errorf("accepted ADR %q is missing or ambiguous", revision)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(revision, "ARCH-") {
|
||||
matches, err := filepath.Glob(filepath.Join(root, "dev", "tasks", revision+"*.json"))
|
||||
if err != nil || len(matches) == 0 {
|
||||
return fmt.Errorf("architecture task %q is missing", revision)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("must reference an accepted ADR-NNNN or architecture task")
|
||||
}
|
||||
|
||||
func validateScopeOverlap(allowed, forbidden []string) error {
|
||||
allowedPatterns, err := compileScopePatterns(allowed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
forbiddenPatterns, err := compileScopePatterns(forbidden)
|
||||
if err != nil {
|
||||
return fmt.Errorf("forbidden_change_scope: %w", err)
|
||||
}
|
||||
for _, a := range allowedPatterns {
|
||||
for _, f := range forbiddenPatterns {
|
||||
if scopePatternsOverlap(a, f) {
|
||||
return fmt.Errorf("allowed path %q overlaps forbidden path %q", a.raw, f.raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scopePatternsOverlap(a, b scopePattern) bool {
|
||||
if a.subtree != "" && b.subtree != "" {
|
||||
return a.subtree == b.subtree || strings.HasPrefix(a.subtree, b.subtree+"/") || strings.HasPrefix(b.subtree, a.subtree+"/")
|
||||
}
|
||||
if a.subtree != "" {
|
||||
return b.raw == a.subtree || strings.HasPrefix(b.raw, a.subtree+"/")
|
||||
}
|
||||
if b.subtree != "" {
|
||||
return a.raw == b.subtree || strings.HasPrefix(a.raw, b.subtree+"/")
|
||||
}
|
||||
return a.raw == b.raw
|
||||
}
|
||||
|
||||
func mustCompile(values []string) []scopePattern {
|
||||
patterns, err := compileScopePatterns(values)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateScopeOverlapRejectsAllowedForbiddenConflict(t *testing.T) {
|
||||
err := validateScopeOverlap([]string{"cmd/creator-web/server.go"}, []string{"cmd/creator-web/**"})
|
||||
if err == nil || !strings.Contains(err.Error(), "overlaps forbidden") {
|
||||
t.Fatalf("overlap error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRequiredTestsRejectsIncompleteEvidence(t *testing.T) {
|
||||
err := validateRequiredTests([]string{"go test ./..."})
|
||||
if err == nil || !strings.Contains(err.Error(), "missing required governance evidence") {
|
||||
t.Fatalf("required test error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDeltaRejectsUnknownOrMissingFields(t *testing.T) {
|
||||
err := validateDelta(map[string]int{"new_privileges": 0})
|
||||
if err == nil || !strings.Contains(err.Error(), "exactly") {
|
||||
t.Fatalf("delta error = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,23 @@ package artifact
|
||||
|
||||
import "git.cin.su/m/Assistant/platform/ids"
|
||||
|
||||
import "errors"
|
||||
|
||||
type ArtifactID ids.ID
|
||||
|
||||
type AgentArtifactAttestation struct {
|
||||
ArtifactID ArtifactID
|
||||
ProducerRunID string
|
||||
SourceRevision string
|
||||
WorktreeID string
|
||||
ContentDigest string
|
||||
TestEvidenceRef string
|
||||
CreatedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (a AgentArtifactAttestation) Validate() error {
|
||||
if a.ArtifactID == "" || a.ProducerRunID == "" || a.SourceRevision == "" || a.WorktreeID == "" || a.ContentDigest == "" || a.TestEvidenceRef == "" || a.CreatedAtUnixMillis < 1 {
|
||||
return errors.New("agent artifact attestation is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package capability
|
||||
|
||||
type AdmissionComplexity string
|
||||
type AdmissionDecisionState string
|
||||
type AdmissionReason string
|
||||
|
||||
const (
|
||||
AdmissionSimple AdmissionComplexity = "SIMPLE"
|
||||
AdmissionNormal AdmissionComplexity = "NORMAL"
|
||||
AdmissionHeavy AdmissionComplexity = "HEAVY"
|
||||
AdmissionAdmitted AdmissionDecisionState = "ADMITTED"
|
||||
AdmissionRejected AdmissionDecisionState = "REJECTED"
|
||||
AdmissionReasonNone AdmissionReason = "NONE"
|
||||
AdmissionReasonConcurrency AdmissionReason = "CONCURRENCY_LIMIT"
|
||||
AdmissionReasonAcceptedHour AdmissionReason = "ACCEPTED_PER_HOUR_LIMIT"
|
||||
AdmissionReasonDeadline AdmissionReason = "DEADLINE"
|
||||
AdmissionReasonInputBudget AdmissionReason = "INPUT_BUDGET"
|
||||
AdmissionReasonOutputBudget AdmissionReason = "OUTPUT_BUDGET"
|
||||
)
|
||||
|
||||
type AdmissionPolicy struct {
|
||||
ID string
|
||||
MaxConcurrent int
|
||||
MaxAcceptedPerHour int
|
||||
MaxInputBytes int
|
||||
MaxOutputBytes int
|
||||
}
|
||||
|
||||
type AdmissionRequest struct {
|
||||
Complexity AdmissionComplexity
|
||||
InputBytes int
|
||||
MaxOutputBytes int
|
||||
DeadlineMillis int64
|
||||
}
|
||||
|
||||
type AdmissionCounters struct {
|
||||
Concurrent int
|
||||
AcceptedLastHour int
|
||||
}
|
||||
|
||||
type AdmissionDecision struct {
|
||||
State AdmissionDecisionState
|
||||
Reason AdmissionReason
|
||||
PolicyID string
|
||||
}
|
||||
|
||||
func Admit(request AdmissionRequest, policy AdmissionPolicy, counters AdmissionCounters, nowMillis int64) AdmissionDecision {
|
||||
decision := AdmissionDecision{State: AdmissionAdmitted, Reason: AdmissionReasonNone, PolicyID: policy.ID}
|
||||
if policy.ID == "" || policy.MaxConcurrent < 1 || policy.MaxAcceptedPerHour < 1 || policy.MaxInputBytes < 1 || policy.MaxOutputBytes < 1 {
|
||||
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonInputBudget, PolicyID: policy.ID}
|
||||
}
|
||||
if counters.Concurrent >= policy.MaxConcurrent {
|
||||
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonConcurrency, PolicyID: policy.ID}
|
||||
}
|
||||
if counters.AcceptedLastHour >= policy.MaxAcceptedPerHour {
|
||||
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonAcceptedHour, PolicyID: policy.ID}
|
||||
}
|
||||
if request.InputBytes < 0 || request.InputBytes > policy.MaxInputBytes {
|
||||
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonInputBudget, PolicyID: policy.ID}
|
||||
}
|
||||
if request.MaxOutputBytes < 1 || request.MaxOutputBytes > policy.MaxOutputBytes {
|
||||
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonOutputBudget, PolicyID: policy.ID}
|
||||
}
|
||||
if request.DeadlineMillis > 0 && request.DeadlineMillis <= nowMillis {
|
||||
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonDeadline, PolicyID: policy.ID}
|
||||
}
|
||||
return decision
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package capability
|
||||
|
||||
import "errors"
|
||||
|
||||
type QualificationManifestID string
|
||||
type QualificationDimension string
|
||||
|
||||
const (
|
||||
DimensionDialogue QualificationDimension = "DIALOGUE"
|
||||
DimensionInterpretation QualificationDimension = "INTERPRETATION"
|
||||
DimensionCoding QualificationDimension = "CODING"
|
||||
DimensionResearch QualificationDimension = "RESEARCH"
|
||||
DimensionUncertainty QualificationDimension = "UNCERTAINTY"
|
||||
DimensionContext QualificationDimension = "CONTEXT"
|
||||
DimensionLatency QualificationDimension = "LATENCY"
|
||||
DimensionResources QualificationDimension = "RESOURCES"
|
||||
DimensionRestart QualificationDimension = "RESTART"
|
||||
)
|
||||
|
||||
type QualificationMeasurement struct {
|
||||
Dimension QualificationDimension
|
||||
Result EvaluationResult
|
||||
Artifact string
|
||||
}
|
||||
|
||||
type QualificationManifest struct {
|
||||
ID QualificationManifestID
|
||||
ModelDescriptorID ModelDescriptorID
|
||||
ModelRevision int64
|
||||
ModelFingerprint ModelFingerprint
|
||||
BenchmarkVersion string
|
||||
HoldoutCorpusDigest string
|
||||
LicenseEvidence string
|
||||
DataPolicyEvidence string
|
||||
Measurements []QualificationMeasurement
|
||||
ObservedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (m QualificationManifest) Validate() error {
|
||||
if m.ID == "" || m.ModelDescriptorID == "" || m.ModelRevision < 1 || m.ModelFingerprint == "" || m.BenchmarkVersion == "" || m.HoldoutCorpusDigest == "" || m.LicenseEvidence == "" || m.DataPolicyEvidence == "" || m.ObservedAtUnixMillis < 1 {
|
||||
return errors.New("qualification manifest identity and evidence are incomplete")
|
||||
}
|
||||
required := map[QualificationDimension]bool{DimensionDialogue: false, DimensionInterpretation: false, DimensionCoding: false, DimensionResearch: false, DimensionUncertainty: false, DimensionContext: false, DimensionLatency: false, DimensionResources: false, DimensionRestart: false}
|
||||
for _, measurement := range m.Measurements {
|
||||
if measurement.Dimension == "" || measurement.Artifact == "" || measurement.Result == EvaluationUnknown {
|
||||
return errors.New("qualification measurement is incomplete")
|
||||
}
|
||||
if _, ok := required[measurement.Dimension]; ok {
|
||||
required[measurement.Dimension] = true
|
||||
}
|
||||
}
|
||||
for _, present := range required {
|
||||
if !present {
|
||||
return errors.New("qualification manifest lacks required measurement")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QualificationState projects evidence into a qualification result. It does
|
||||
// not activate a binding or mutate the model registry.
|
||||
func (m QualificationManifest) QualificationState() QualificationState {
|
||||
if m.Validate() != nil {
|
||||
return QualificationRejected
|
||||
}
|
||||
for _, measurement := range m.Measurements {
|
||||
if measurement.Result != EvaluationSupported {
|
||||
return QualificationRejected
|
||||
}
|
||||
}
|
||||
return QualificationValidated
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package cognitive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
var ErrInvalidContextBudget = errors.New("context budget must be positive")
|
||||
|
||||
type ContextCandidate struct {
|
||||
Excerpt ContextExcerpt
|
||||
Relevance int
|
||||
}
|
||||
|
||||
type ContextCompileInput struct {
|
||||
Candidates []ContextCandidate
|
||||
DataPolicy capability.DataPolicy
|
||||
MaxBytes int
|
||||
}
|
||||
|
||||
type ContextOmission struct {
|
||||
ID ContextExcerptID
|
||||
Reason string
|
||||
}
|
||||
|
||||
type ContextBundle struct {
|
||||
Excerpts []ContextExcerpt
|
||||
Omissions []ContextOmission
|
||||
Bytes int
|
||||
}
|
||||
|
||||
// CompileContext deterministically selects permitted, relevant excerpts for a
|
||||
// gateway request. It carries no provider identity and performs no I/O.
|
||||
func CompileContext(input ContextCompileInput) (ContextBundle, error) {
|
||||
if input.MaxBytes <= 0 {
|
||||
return ContextBundle{}, ErrInvalidContextBudget
|
||||
}
|
||||
candidates := append([]ContextCandidate(nil), input.Candidates...)
|
||||
sort.SliceStable(candidates, func(i, j int) bool {
|
||||
if candidates[i].Relevance != candidates[j].Relevance {
|
||||
return candidates[i].Relevance > candidates[j].Relevance
|
||||
}
|
||||
return string(candidates[i].Excerpt.ID) < string(candidates[j].Excerpt.ID)
|
||||
})
|
||||
bundle := ContextBundle{Excerpts: []ContextExcerpt{}, Omissions: []ContextOmission{}}
|
||||
seen := make(map[ContextExcerptID]struct{}, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
excerpt := candidate.Excerpt
|
||||
if _, ok := seen[excerpt.ID]; ok {
|
||||
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "duplicate"})
|
||||
continue
|
||||
}
|
||||
seen[excerpt.ID] = struct{}{}
|
||||
reason := permittedContextReason(excerpt, input.DataPolicy)
|
||||
if reason != "" {
|
||||
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: reason})
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(excerpt.Text) == "" {
|
||||
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "empty"})
|
||||
continue
|
||||
}
|
||||
bytes := len([]byte(excerpt.Text))
|
||||
if bundle.Bytes+bytes > input.MaxBytes {
|
||||
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "budget"})
|
||||
continue
|
||||
}
|
||||
bundle.Excerpts = append(bundle.Excerpts, excerpt)
|
||||
bundle.Bytes += bytes
|
||||
}
|
||||
return bundle, nil
|
||||
}
|
||||
|
||||
func permittedContextReason(excerpt ContextExcerpt, policy capability.DataPolicy) string {
|
||||
if excerpt.Sensitivity == SensitivitySecret {
|
||||
return "secret"
|
||||
}
|
||||
if excerpt.Sensitivity == SensitivityUnknown {
|
||||
return "unknown_sensitivity"
|
||||
}
|
||||
if policy == capability.DataPolicyExternalMessageOnly {
|
||||
return "policy_message_only"
|
||||
}
|
||||
if policy == capability.DataPolicyExternalMinimizedContext && excerpt.Sensitivity != SensitivityPublic {
|
||||
return "policy_public_only"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package epistemics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrCitationEvidenceMismatch = errors.New("citation evidence does not match source chain")
|
||||
|
||||
type ResearchPlanID string
|
||||
type SourceID string
|
||||
type SourceSnapshotID string
|
||||
type SourceExcerptID string
|
||||
type CitationID string
|
||||
type FreshnessState string
|
||||
type CoverageState string
|
||||
type ConflictState string
|
||||
type EvidenceKind string
|
||||
|
||||
const (
|
||||
FreshnessCurrent FreshnessState = "CURRENT"
|
||||
FreshnessStale FreshnessState = "STALE"
|
||||
FreshnessUnknown FreshnessState = "UNKNOWN"
|
||||
CoverageComplete CoverageState = "COMPLETE"
|
||||
CoveragePartial CoverageState = "PARTIAL"
|
||||
CoverageUnknown CoverageState = "UNKNOWN"
|
||||
ConflictNone ConflictState = "NONE"
|
||||
ConflictPresent ConflictState = "PRESENT"
|
||||
ConflictUnknown ConflictState = "UNKNOWN"
|
||||
EvidenceObserved EvidenceKind = "OBSERVED"
|
||||
EvidenceAssumed EvidenceKind = "ASSUMED"
|
||||
)
|
||||
|
||||
type ResearchPlan struct {
|
||||
ID ResearchPlanID
|
||||
Question string
|
||||
SourceClasses []string
|
||||
FreshnessPolicy string
|
||||
CreatedAtUnixMillis int64
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
ID SourceID
|
||||
Kind string
|
||||
Locator string
|
||||
Title string
|
||||
Publisher string
|
||||
}
|
||||
|
||||
type SourceSnapshot struct {
|
||||
ID SourceSnapshotID
|
||||
SourceID SourceID
|
||||
ContentDigest string
|
||||
CapturedAtUnixMillis int64
|
||||
Freshness FreshnessState
|
||||
}
|
||||
|
||||
type SourceExcerpt struct {
|
||||
ID SourceExcerptID
|
||||
SnapshotID SourceSnapshotID
|
||||
Text string
|
||||
Start int64
|
||||
End int64
|
||||
}
|
||||
|
||||
type Citation struct {
|
||||
ID CitationID
|
||||
ExcerptID SourceExcerptID
|
||||
Evidence EvidenceKind
|
||||
Coverage CoverageState
|
||||
Conflict ConflictState
|
||||
ResolvedAtUnixMillis int64
|
||||
}
|
||||
|
||||
// ResolveCitation creates evidence only when all identifiers form one exact
|
||||
// source chain. It deliberately does not fetch, infer or rewrite source text.
|
||||
func ResolveCitation(source Source, snapshot SourceSnapshot, excerpt SourceExcerpt, evidence EvidenceKind, coverage CoverageState, conflict ConflictState, resolvedAtUnixMillis int64) (Citation, error) {
|
||||
if source.ID == "" || strings.TrimSpace(source.Locator) == "" || snapshot.SourceID != source.ID || excerpt.SnapshotID != snapshot.ID || excerpt.Validate() != nil || resolvedAtUnixMillis < 1 || evidence == "" || coverage == "" || conflict == "" {
|
||||
return Citation{}, ErrCitationEvidenceMismatch
|
||||
}
|
||||
citation := Citation{ID: CitationID("citation:" + string(excerpt.ID)), ExcerptID: excerpt.ID, Evidence: evidence, Coverage: coverage, Conflict: conflict, ResolvedAtUnixMillis: resolvedAtUnixMillis}
|
||||
return citation, citation.Validate()
|
||||
}
|
||||
|
||||
func (p ResearchPlan) Validate() error {
|
||||
if p.ID == "" || strings.TrimSpace(p.Question) == "" || len(p.SourceClasses) == 0 || strings.TrimSpace(p.FreshnessPolicy) == "" {
|
||||
return errors.New("research plan requires question, source classes and freshness policy")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SourceSnapshot) Validate() error {
|
||||
if s.ID == "" || s.SourceID == "" || strings.TrimSpace(s.ContentDigest) == "" || s.CapturedAtUnixMillis < 1 || s.Freshness == "" {
|
||||
return errors.New("source snapshot requires source, digest, capture time and freshness")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e SourceExcerpt) Validate() error {
|
||||
if e.ID == "" || e.SnapshotID == "" || strings.TrimSpace(e.Text) == "" || e.Start < 0 || e.End < e.Start {
|
||||
return errors.New("source excerpt requires snapshot, text and valid offsets")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Citation) Validate() error {
|
||||
if c.ID == "" || c.ExcerptID == "" || c.Evidence == "" || c.Coverage == "" || c.Conflict == "" || c.ResolvedAtUnixMillis < 1 {
|
||||
return errors.New("citation requires exact excerpt, evidence, coverage, conflict and resolution time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package work
|
||||
|
||||
import "time"
|
||||
|
||||
type DryRunDispatch struct {
|
||||
Intent AgentDispatchIntent
|
||||
Lease AgentLease
|
||||
Callbacks []AgentCallback
|
||||
}
|
||||
|
||||
func (value DryRunDispatch) CallbackCount() int { return len(value.Callbacks) }
|
||||
|
||||
func (value DryRunDispatch) LastObservedAt() time.Time {
|
||||
if len(value.Callbacks) == 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
return value.Callbacks[len(value.Callbacks)-1].ObservedAt
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package work
|
||||
|
||||
import "time"
|
||||
|
||||
type AgentLeaseID string
|
||||
type AgentFencingToken uint64
|
||||
type AgentLeaseState string
|
||||
|
||||
const (
|
||||
AgentLeaseActive AgentLeaseState = "ACTIVE"
|
||||
AgentLeaseExpired AgentLeaseState = "EXPIRED"
|
||||
)
|
||||
|
||||
type AgentLease struct {
|
||||
ID AgentLeaseID
|
||||
RunID AgentRunID
|
||||
WorkerID string
|
||||
FencingToken AgentFencingToken
|
||||
State AgentLeaseState
|
||||
AcquiredAt time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type AgentCallbackKind string
|
||||
|
||||
const (
|
||||
AgentCallbackHeartbeat AgentCallbackKind = "HEARTBEAT"
|
||||
AgentCallbackCheckpoint AgentCallbackKind = "CHECKPOINT"
|
||||
AgentCallbackResult AgentCallbackKind = "RESULT"
|
||||
AgentCallbackCancelAcknowledged AgentCallbackKind = "CANCEL_ACKNOWLEDGED"
|
||||
)
|
||||
|
||||
type AgentCallback struct {
|
||||
RunID AgentRunID
|
||||
WorkerID string
|
||||
FencingToken AgentFencingToken
|
||||
Sequence uint64
|
||||
Kind AgentCallbackKind
|
||||
ObservedAt time.Time
|
||||
}
|
||||
|
||||
type IndependentVerification struct {
|
||||
ProducerRunID AgentRunID
|
||||
VerifierRunID AgentRunID
|
||||
VerifierIdentity string
|
||||
ArtifactID string
|
||||
TestEvidenceRef string
|
||||
Verified bool
|
||||
Summary string
|
||||
}
|
||||
|
||||
type AgentDispatchIntent struct {
|
||||
DispatchID string
|
||||
IdempotencyKey string
|
||||
DelegationID DelegationID
|
||||
RunID AgentRunID
|
||||
ImplementationID string
|
||||
ImplementationRevision int64
|
||||
SourceRevision string
|
||||
WorktreeID string
|
||||
VerificationPlanRef string
|
||||
CreatedAtUnixMillis int64
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
BEGIN;
|
||||
|
||||
SET ROLE work_role;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.agent_dispatch_intents (
|
||||
dispatch_id TEXT PRIMARY KEY,
|
||||
idempotency_key TEXT NOT NULL UNIQUE,
|
||||
delegation_id TEXT NOT NULL,
|
||||
run_id TEXT NOT NULL,
|
||||
implementation_id TEXT NOT NULL,
|
||||
implementation_revision BIGINT NOT NULL,
|
||||
source_revision TEXT NOT NULL,
|
||||
worktree_id TEXT NOT NULL,
|
||||
verification_plan_ref TEXT NOT NULL,
|
||||
created_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (dispatch_id <> ''), CHECK (idempotency_key <> ''), CHECK (delegation_id <> ''),
|
||||
CHECK (run_id <> ''), CHECK (implementation_id <> ''), CHECK (implementation_revision > 0),
|
||||
CHECK (source_revision <> ''), CHECK (worktree_id <> ''), CHECK (verification_plan_ref <> ''),
|
||||
CHECK (created_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.agent_leases (
|
||||
lease_id TEXT PRIMARY KEY,
|
||||
run_id TEXT NOT NULL UNIQUE,
|
||||
worker_id TEXT NOT NULL,
|
||||
fencing_token BIGINT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
acquired_at_unix_millis BIGINT NOT NULL,
|
||||
expires_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (lease_id <> ''), CHECK (run_id <> ''), CHECK (worker_id <> ''),
|
||||
CHECK (fencing_token > 0), CHECK (state IN ('ACTIVE','EXPIRED')),
|
||||
CHECK (acquired_at_unix_millis > 0), CHECK (expires_at_unix_millis > acquired_at_unix_millis)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.agent_callback_heads (
|
||||
run_id TEXT PRIMARY KEY,
|
||||
last_sequence BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (run_id <> ''), CHECK (last_sequence >= 0), CHECK (updated_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -84,6 +84,19 @@ services:
|
||||
volumes:
|
||||
- assistant_core_secrets:/run/secrets:ro
|
||||
|
||||
agent-canary:
|
||||
profiles: ["agent-canary"]
|
||||
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: agent-canary, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
|
||||
restart: "no"
|
||||
command: ["-enable-canary", "-postgres"]
|
||||
environment:
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_DATABASE: ${POSTGRES_DB:-assistant}
|
||||
WORK_POSTGRES_USER: work_role
|
||||
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD}
|
||||
depends_on: { postgres: { condition: service_healthy } }
|
||||
|
||||
continuity-runtime:
|
||||
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: continuity-runtime, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -4,10 +4,15 @@ set -eu
|
||||
backup_root="${ASSISTANT_BACKUP_DIR:-/home/m/assistant-backups/postgres}"
|
||||
verify_script="${ASSISTANT_RESTORE_VERIFY_SCRIPT:-/home/m/assistant-ops/verify-postgres-restore.sh}"
|
||||
|
||||
latest="$(find "$backup_root" -mindepth 1 -maxdepth 1 -type d ! -name '.*' -printf '%f\n' | sort | tail -n 1)"
|
||||
latest="$(find "$backup_root" -mindepth 1 -maxdepth 1 -type d -regextype posix-extended -regex '.*/[0-9]{8}T[0-9]{6}Z$' -printf '%f\n' | sort | tail -n 1)"
|
||||
if [ -z "$latest" ]; then
|
||||
echo "No completed PostgreSQL backup found in $backup_root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$latest" in
|
||||
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z) ;;
|
||||
*) echo "Selected backup directory has invalid timestamp: $latest" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
exec "$verify_script" "$backup_root/$latest"
|
||||
|
||||
@@ -45,7 +45,7 @@ $cipherPartial = $null
|
||||
$manifestPartial = $null
|
||||
|
||||
try {
|
||||
$latest = ((& ssh.exe $SshHost "find '$RemoteBackupRoot' -mindepth 1 -maxdepth 1 -type d ! -name '.*' -printf '%f\n' | sort | tail -n1") -join '').Trim()
|
||||
$latest = ((& ssh.exe $SshHost "find '$RemoteBackupRoot' -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | grep -E '^[0-9]{8}T[0-9]{6}Z$' | sort | tail -n1") -join '').Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or $latest -notmatch '^\d{8}T\d{6}Z$') { throw 'No valid completed production backup found' }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $NasBackupRoot | Out-Null
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000096-builtin-cognitive-usage-telemetry.json",
|
||||
"task_id": "DEV-000096",
|
||||
"task_file": "dev/tasks/DEV-000128-agent-canary-compose-profile.json",
|
||||
"task_id": "DEV-000128",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"task_id": "DEV-000098",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "b439a813e5932b1774cb3b6c2364c6cac3891314",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement Governance Sentinel V2 so task packets are fully validated and required governance evidence is enforced before promotion.",
|
||||
"requirement_ids": ["WP-000", "CONSTITUTION-1.3.27", "CONSTITUTION-1.3.28"],
|
||||
"allowed_change_scope": {
|
||||
"modules": [],
|
||||
"paths": ["cmd/devctl/**", ".gitea/workflows/ci.yml", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000098-governance-sentinel-v2.json"]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": ["architecture/ARCHITECTURE_CONSTITUTION.md", "architecture/CORE_SEMANTIC_MODEL_V1.md", "security/**", "database/**", "deploy/**", "modules/**", "contracts/**", "adapters/**", "platform/**"]
|
||||
},
|
||||
"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": ["Architecture registry", "Accepted ADR files", "Codex Task Packet JSON", "Git repository state"], "may_change": []},
|
||||
"semantic_owner": "none-architecture-guardrail",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["Task packet validation returns pass or typed gap"],
|
||||
"side_effects_and_privileges": ["Reads repository metadata and required test commands; no external mutation or secret access"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Malformed, incomplete, conflicting or unverifiable packets fail closed; checks are deterministic and rerunnable"],
|
||||
"assumptions": ["ADR-0018 is the current accepted architecture revision", "Independent verification remains a CI/review responsibility", "docker-test remains prohibited"],
|
||||
"acceptance_scenarios": ["Full packet fields are strictly parsed", "Allowed and forbidden scope overlap is rejected", "Architecture revision existence and base ancestry are checked", "Architecture deltas, dependencies, contracts, privileges and required tests are checked", "Tier E does not bypass validation", "CI executes the required governance checks"],
|
||||
"required_tests": ["go test ./cmd/devctl", "go run ./cmd/devctl/independent", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go vet ./...", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_MISSING_CAPABILITY"]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"task_id": "DEV-000099",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "ff33a62",
|
||||
"risk_tier": "E",
|
||||
"intent": "Consolidate active task lineage, project status and architecture registry mirrors after the audit without changing runtime behavior or canonical ownership.",
|
||||
"requirement_ids": ["WP-001", "ADR-0017", "Cold-agent governance reproducibility"],
|
||||
"allowed_change_scope": {
|
||||
"modules": [],
|
||||
"paths": ["architecture/architecture-registry.yaml", "architecture/architecture-registry.json", "architecture/architecture-registry.md", "docs/PROJECT_STATUS.md", "docs/GOVERNANCE_LINEAGE_DECISION_2026-08-22.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000099-governance-consolidation.json", "cmd/devctl/**"]
|
||||
},
|
||||
"forbidden_change_scope": {"paths": ["architecture/ARCHITECTURE_CONSTITUTION.md", "architecture/adrs/**", "security/**", "database/**", "deploy/**", "modules/**", "contracts/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 ownership decision", "Architecture registry YAML/JSON", "Active task pointer", "Project status evidence"], "may_change": []},
|
||||
"semantic_owner": "none-governance-projection",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["DEV-000098 completed -> DEV-000099 active -> DEV-000099 completed"],
|
||||
"side_effects_and_privileges": ["Repository documentation and governance metadata only; no runtime or production mutation"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Historical checkpoints remain append-only; ambiguous lineage is recorded as a decision rather than silently rewritten"],
|
||||
"assumptions": ["ADR-0017 remains accepted", "YAML and JSON registry are semantically equivalent authority mirrors", "docker-test remains prohibited"],
|
||||
"acceptance_scenarios": ["Active pointer names the current packet and revision", "DEV-000073 stale lineage is explicitly resolved", "PROJECT_STATUS starts with current audit/WP-001 evidence while preserving historical checkpoints", "YAML/JSON/human registry agree on ADR-0017 ownership", "Cold agent can reconstruct current ownership and task ancestry from repository files"],
|
||||
"required_tests": ["go test ./cmd/devctl", "go run ./cmd/devctl/independent", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go vet ./...", "git diff --check", "registry YAML/JSON parity"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_MISSING_CAPABILITY"]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"task_id": "DEV-000100",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "8687c9b",
|
||||
"risk_tier": "E",
|
||||
"intent": "Repair production backup and restore selection so only complete timestamped backups are eligible, and make provenance drift degrade readiness.",
|
||||
"requirement_ids": ["WP-002", "INV-CONT-001", "Production recovery must be reversible"],
|
||||
"allowed_change_scope": {"modules": [], "paths": ["deploy/verify-latest-postgres-backup.sh", "deploy/windows/Sync-PostgresBackupToNas.ps1", "deploy/windows/Test-DomovoyProductionHealth.ps1", "tests/production_recovery/**", "docs/DEPLOYMENT.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000100-production-recovery-repair.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "contracts/**", "modules/**", "cmd/**", "adapters/**", "platform/**", ".gitea/**"]},
|
||||
"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": ["Production backup directory contract", "Deployment manifest evidence", "Production health projection"], "may_change": []},
|
||||
"semantic_owner": "none-operational-recovery", "canonical_state": [],
|
||||
"state_transitions": ["ineligible backup -> ignored", "verified backup -> restore candidate", "provenance/backup drift -> degraded health"],
|
||||
"side_effects_and_privileges": ["Local script changes are read-only until explicit production verification; no secret bytes are logged"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Partial/temp/pre-* directories remain untouched and are ignored; restore checks use isolated temporary PostgreSQL only"],
|
||||
"assumptions": ["Production host is docker.cin.su via docker-prod/docker SSH configuration", "docker-test remains prohibited"],
|
||||
"acceptance_scenarios": ["pre-*, partial and temp directories are ignored", "latest timestamp directory is selected deterministically", "missing/hash-invalid backup is rejected", "production health is degraded on backup or deployment provenance drift", "restore verification remains isolated and cleanup-safe"],
|
||||
"required_tests": ["shell fixture tests", "PowerShell parser", "go run ./cmd/devctl/independent", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go test ./...", "go vet ./...", "git diff --check", "production read-only health evidence"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"task_id": "DEV-000101",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "15c9517",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add dependency-aware readiness projections with typed degraded reasons for core, continuity, commit, cognitive and lifeboat runtimes without changing canonical ownership.",
|
||||
"requirement_ids": ["WP-003", "Typed degraded dependency reasons", "Frontend health distinct from product readiness"],
|
||||
"allowed_change_scope": {"modules": ["development-governance"], "paths": ["cmd/core-runtime/**", "cmd/continuity-runtime/**", "cmd/commit-runtime/**", "cmd/cognitive-worker/**", "runtime/health/**", "tests/readiness/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000101-dependency-aware-readiness.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "modules/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["Existing runtime health handlers", "Existing owner-local repository health", "Existing cognitive capability health"], "may_change": []},
|
||||
"semantic_owner": "none-readiness-projection", "canonical_state": [],
|
||||
"state_transitions": ["dependency available -> ready", "dependency unavailable/unknown -> typed degraded readiness"],
|
||||
"side_effects_and_privileges": ["Read-only dependency probes; no canonical mutation, external mutation or credential expansion"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Probe timeout is UNKNOWN/DEGRADED, not false healthy; recovery returns ready on next probe"],
|
||||
"assumptions": ["Existing /healthz endpoints remain backward compatible", "Frontend health and product readiness are separate projections", "docker-test remains prohibited"],
|
||||
"acceptance_scenarios": ["Stopping each dependency yields the correct typed degraded reason", "DB owner connectivity is represented without cross-owner writes", "cognitive binding/model freshness and lifeboat availability are visible", "recovery returns readiness without restart", "health endpoint does not claim product readiness"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl/independent", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "readiness dependency-stop integration tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000102",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "3f2b008",
|
||||
"risk_tier": "E",
|
||||
"intent": "Promote the dependency-aware readiness implementation to a controlled production canary with explicit rollback evidence, without changing deployment topology or source contracts.",
|
||||
"requirement_ids": ["WP-003", "Controlled canary promotion", "Production rollback evidence"],
|
||||
"allowed_change_scope": {"modules": ["deployment-governance"], "paths": ["dev/ACTIVE_TASK.json", "dev/tasks/DEV-000102-readiness-canary-promotion.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "modules/**", "adapters/**", ".gitea/**", "cmd/**", "runtime/**"]},
|
||||
"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": ["Existing production compose topology", "Existing runtime health endpoints", "Existing production backup and deployment verification"], "may_change": []},
|
||||
"semantic_owner": "none-deployment-canary",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["built candidate -> canary verified", "canary failure -> rollback to recorded production revision"],
|
||||
"side_effects_and_privileges": ["Controlled production container replacement only after build/config verification; no schema, credential, or external data mutation"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Rollback target is production revision b439a813e5932b1774cb3b6c2364c6cac3891314 and recorded core image digest sha256:4fb09995df8efbbb02359e30171f593f9cf9b90489d4ac0bf85a62ed2504ac92"],
|
||||
"assumptions": ["Production control plane is docker-prod/docker.cin.su only", "Canary is limited to readiness-capable runtime services", "Existing secrets and persistent volumes remain unchanged"],
|
||||
"acceptance_scenarios": ["Candidate images build from 3f2b008", "Compose configuration validates without secret disclosure", "Canary readiness returns HTTP 200 with dependency checks", "Existing health and HTTPS checks remain healthy", "Rollback command and pre-canary image identity are recorded"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl independent", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "docker compose config on docker-prod", "production canary readiness and rollback verification"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000103",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "6fb9184",
|
||||
"risk_tier": "E",
|
||||
"intent": "Prepare the proposed ADR-0019 Truth V1 boundary for review without implementing runtime, schema, connector, source or authority changes.",
|
||||
"requirement_ids": ["WP-100", "Observation/Claim/BeliefAssessment", "Source provenance freshness conflict"],
|
||||
"allowed_change_scope": {"modules": ["epistemics-governance"], "paths": ["docs/proposals/ADR-0019-epistemics-truth-v1.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000103-epistemics-truth-v1-proposal.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "contracts/**", "database/**", "migrations/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", "deploy/**", ".gitea/**"]},
|
||||
"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": ["ADR-0016 Epistemics experience/reflection ownership", "ADR-0017 typed provenance references", "Core semantic model ownership boundaries"], "may_change": ["Proposed ADR-0019 only; no accepted contract changes"]},
|
||||
"semantic_owner": "epistemics-proposal-only",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["observation/source evidence -> claim assessment", "assessment -> supported/corroborated/conflicted/stale/retracted/unknown", "correction -> superseding assessment"],
|
||||
"side_effects_and_privileges": ["No runtime side effects, external mutation, connector access, credential access or authority expansion"],
|
||||
"persistence_and_migrations": ["No schema, migration or retention job changes"],
|
||||
"failure_retry_restart": ["Proposal failure leaves no canonical state; accepted implementation must preserve append-only evidence and rebuildable projections"],
|
||||
"assumptions": ["Proposal is reviewed before acceptance", "Epistemics remains the only proposed truth-evidence owner", "Secrets and unapproved personal data are fail-closed"],
|
||||
"acceptance_scenarios": ["Owner boundaries and typed references are explicit", "Unknown, assumed, stale and conflicted states cannot silently become facts", "Correction and supersession preserve history", "Freshness and coverage are separate from confidence", "No implementation or architecture registry delta is introduced"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "proposal scope and required-section review"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000104",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "339f6a6",
|
||||
"risk_tier": "E",
|
||||
"intent": "Prepare the proposed ADR-0020 Agent Execution Plane boundary for review without implementing dispatcher, worker, schema, callback or deployment changes.",
|
||||
"requirement_ids": ["WP-300", "Durable dispatch lease heartbeat", "Independent verification and truthful UNKNOWN outcome"],
|
||||
"allowed_change_scope": {"modules": ["work-execution-governance"], "paths": ["docs/proposals/ADR-0020-agent-execution-plane-v1.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000104-agent-execution-plane-proposal.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "contracts/**", "database/**", "migrations/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", "deploy/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 General Agent Delegation and Codex Bridge V1", "Proposed ADR-0019 Epistemics Truth V1", "Core semantic model Work/Capability/Artifact/Continuity boundaries"], "may_change": ["Proposed ADR-0020 only; no accepted contract changes"]},
|
||||
"semantic_owner": "work-execution-plane-proposal-only",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["dispatch -> leased -> running -> result -> independently verified", "lease expiry/callback uncertainty -> UNKNOWN or RECOVERING", "cancel request -> acknowledged cancellation or typed unknown"],
|
||||
"side_effects_and_privileges": ["No runtime side effects, external mutation, connector access, credential access or authority expansion"],
|
||||
"persistence_and_migrations": ["No schema, migration, queue, worker or lease implementation changes"],
|
||||
"failure_retry_restart": ["Retry creates a new run and preserves history; late or duplicate callbacks cannot become authoritative"],
|
||||
"assumptions": ["Work remains canonical owner of durable delegation execution", "Artifact remains canonical owner of attestation metadata", "Production deployment remains outside this proposal"],
|
||||
"acceptance_scenarios": ["Worker identity and dispatch idempotency are explicit", "Lease fencing and heartbeat expiry are explicit", "Callback authentication/replay handling is explicit", "Cancellation/retry/UNKNOWN semantics preserve history", "Independent verification gates Work completion", "No implementation or registry delta is introduced"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "proposal scope and required-section review"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000105",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "82ee6e5",
|
||||
"risk_tier": "E",
|
||||
"intent": "Prepare the proposed ADR-0021 Multi-Agent Work Graph boundary for review without implementing a scheduler, graph schema, workers or production changes.",
|
||||
"requirement_ids": ["WP-400", "DAG revision and budgets", "Fan-out/fan-in conflict and independent verification"],
|
||||
"allowed_change_scope": {"modules": ["work-graph-governance"], "paths": ["docs/proposals/ADR-0021-multi-agent-work-graph-v1.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000105-multi-agent-work-graph-proposal.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "contracts/**", "database/**", "migrations/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", "deploy/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 delegation ownership", "Proposed ADR-0020 Agent Execution Plane", "Proposed ADR-0019 Truth V1 provenance", "Core Work/Capability/Artifact/Continuity ownership"], "may_change": ["Proposed ADR-0021 only; no accepted contract changes"]},
|
||||
"semantic_owner": "work-graph-proposal-only",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["graph draft -> admitted -> running -> verifying -> completed/partial/failed", "node prerequisite blocked -> ready -> leased/running -> result or recovery", "candidate disagreement -> conflict set -> verified synthesis or unresolved conflict"],
|
||||
"side_effects_and_privileges": ["No runtime side effects, external mutation, connector access, credential access or authority expansion"],
|
||||
"persistence_and_migrations": ["No graph schema, scheduler, queue, lease or migration implementation changes"],
|
||||
"failure_retry_restart": ["Graph revisions are immutable; restart/replay uses durable history; unknown outcomes block unsafe replay"],
|
||||
"assumptions": ["Work remains canonical graph owner", "Roles are capability profiles without authority", "Independent verification is required before Work completion"],
|
||||
"acceptance_scenarios": ["Cycles are rejected before admission", "Budgets cover graph/user/resource concurrency and attempts", "Fan-out/fan-in and partial failure preserve attribution", "Cancellation/pause/resume/restart semantics are explicit", "Conflicts retain dissent and verifier identity is distinct", "No implementation or registry delta is introduced"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "proposal scope and required-section review"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000106",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "08deab8",
|
||||
"risk_tier": "E",
|
||||
"intent": "Prepare the proposed ADR-0022 Governed Self-Development boundary for review without implementing proposal generation, factories, candidate activation or production changes.",
|
||||
"requirement_ids": ["WP-600", "Governed development control plane", "Candidate canary promotion rollback emergency stop"],
|
||||
"allowed_change_scope": {"modules": ["governed-development-proposal"], "paths": ["docs/proposals/ADR-0022-governed-self-development-v1.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000106-governed-self-development-proposal.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "contracts/**", "database/**", "migrations/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", "deploy/**", ".gitea/**", "skills/**"]},
|
||||
"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": ["ADR-0016 Experience/Reflection/Skill Evolution", "Proposed ADR-0019 Truth V1 evidence", "Proposed ADR-0020 Agent Execution Plane", "Proposed ADR-0021 Work Graph", "existing Authority/Commit/Artifact boundaries"], "may_change": ["Proposed ADR-0022 only; no accepted contract changes"]},
|
||||
"semantic_owner": "governed-development-proposal-only",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["evidence -> proposal -> sandbox -> evaluation -> canary -> promotion/rollback/retirement", "failed/conflicted/incomplete evaluation -> blocked"],
|
||||
"side_effects_and_privileges": ["No runtime side effects, source mutation, deployment, external mutation, credential access or authority expansion"],
|
||||
"persistence_and_migrations": ["No proposal store, candidate artifact store, schema, migration or telemetry implementation changes"],
|
||||
"failure_retry_restart": ["Candidate evidence and lineage survive rejection/rollback; emergency stop disables candidate selection without deleting history"],
|
||||
"assumptions": ["Human governance remains promotion authority", "Protected paths and secret policies cannot be modified by the control plane", "New code/model/skill never receives authority automatically"],
|
||||
"acceptance_scenarios": ["One failure cannot create a proposal", "Candidate lineage and isolated scope are mandatory", "Independent evaluation and verifier are explicit", "Canary and rollback preserve previous implementation and evidence", "Emergency stop blocks candidate traffic", "No implementation or registry delta is introduced"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "proposal scope and required-section review"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000107",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "3e0be62",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement a typed context compiler for relevance, sensitivity, provenance and byte budgets at the cognitive contract boundary.",
|
||||
"requirement_ids": ["WP-101", "Typed context bundle", "Sensitivity/egress filtering and budget"],
|
||||
"allowed_change_scope": {"modules": ["cognitive-contracts"], "paths": ["contracts/cognitive/**", "tests/context_compiler/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000107-context-compiler-v1.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["Existing cognitive ContextExcerpt and Gateway request boundary", "Capability DataPolicy values"], "may_change": ["Cognitive context bundle/compiler contract"]},
|
||||
"semantic_owner": "cognitive-context-contract",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["candidate excerpts -> selected typed context bundle", "budget/sensitivity decision -> included or omitted with reason"],
|
||||
"side_effects_and_privileges": ["Pure deterministic compilation; no provider call, persistence, external mutation or credential access"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Compilation is deterministic and repeatable; empty permitted context remains a valid bundle"],
|
||||
"assumptions": ["Gateway remains responsible for provider selection", "Secret and unknown excerpts are never emitted", "Provider identity remains invocation provenance"],
|
||||
"acceptance_scenarios": ["Relevant excerpts are selected deterministically", "Duplicate and low-value excerpts do not consume budget", "Secret/unknown data is omitted", "External minimized policy excludes private data", "UTF-8 byte budget is respected", "Provider identity is not represented as persona"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "context compiler tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000108",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "879de3b",
|
||||
"risk_tier": "E",
|
||||
"intent": "Use the typed context compiler for local chat cognition with the canonical active-project focus, keeping external advisory context empty unless explicitly supplied by a future policy-aware path.",
|
||||
"requirement_ids": ["WP-101", "Follow-up context", "Active project isolation"],
|
||||
"allowed_change_scope": {"modules": ["creator-gateway", "cognitive-contracts"], "paths": ["cmd/creator-web/**", "contracts/cognitive/**", "tests/context_compiler/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000108-context-compiler-gateway-integration.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["ContextBundle compiler from DEV-000107", "Core ChatMessageResponse active project fields", "Existing local cognitive gateway request"], "may_change": ["Creator gateway context assembly"]},
|
||||
"semantic_owner": "creator-gateway-context-projection",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["canonical active project -> local context candidate -> compiled gateway context", "missing active project -> empty context"],
|
||||
"side_effects_and_privileges": ["Context assembly is read-only; no persistence, provider selection, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No database schema or migration changes"],
|
||||
"failure_retry_restart": ["Compiler failure or absent focus yields empty context and preserves the existing chat fallback"],
|
||||
"assumptions": ["External advisory remains message-only until a separate policy-aware context path exists", "Active project name is already canonicalized by Core", "Current project context is not reused for unrelated requests"],
|
||||
"acceptance_scenarios": ["Local cognitive request receives the active project as provenance-bearing context", "No active project yields empty context", "External advisory receives no implicit private context", "Project context does not leak into a different saved response", "Existing creator gateway and full tests remain green"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "context compiler gateway integration tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000109",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "e3b55c9",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add typed read-only research and citation evidence contracts for plans, source snapshots, excerpts and freshness/conflict state without connecting external sources.",
|
||||
"requirement_ids": ["WP-102", "Source snapshot and citation resolver", "UNKNOWN/ASSUMPTION freshness conflict"],
|
||||
"allowed_change_scope": {"modules": ["epistemics-research-contracts"], "paths": ["contracts/epistemics/**", "tests/research_contracts/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000109-research-citation-contracts.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["Proposed ADR-0019 Truth V1 evidence/provenance", "Existing Epistemics owner", "Existing Artifact typed reference boundary"], "may_change": ["Epistemics research/citation contract"]},
|
||||
"semantic_owner": "epistemics-research-contract",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["research plan -> source snapshot -> excerpt -> citation", "source conflict/freshness result -> explicit claim status"],
|
||||
"side_effects_and_privileges": ["Typed value validation only; no connector calls, external mutation, credentials or persistence"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Unknown source/retrieval state is retained as UNKNOWN and cannot become a citation silently"],
|
||||
"assumptions": ["Connectors and durable storage arrive in later tasks", "Citation references an exact excerpt identity and snapshot", "Research output is read-only evidence"],
|
||||
"acceptance_scenarios": ["Plan identifies question, source classes and freshness policy", "Snapshot has content digest and retrieval timestamp", "Citation requires exact excerpt and source snapshot", "stale/conflicted/unknown states remain explicit", "assumption is distinguishable from observed evidence"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "research contract tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000110",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "dc7f7b3",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add a deterministic citation resolver that requires a matching source, immutable snapshot and exact excerpt before producing citation evidence.",
|
||||
"requirement_ids": ["WP-102", "Citation resolver", "No fabricated excerpts"],
|
||||
"allowed_change_scope": {"modules": ["epistemics-research-contracts"], "paths": ["contracts/epistemics/**", "tests/research_contracts/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000110-citation-resolver.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["DEV-000109 research/citation contracts", "Epistemics source snapshot and excerpt types"], "may_change": ["Citation resolution helper"]},
|
||||
"semantic_owner": "epistemics-research-contract",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["source/snapshot/excerpt evidence -> resolved citation", "mismatched evidence -> resolution error"],
|
||||
"side_effects_and_privileges": ["Pure deterministic validation; no connector calls, persistence, external mutation or credential access"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Resolution is repeatable; failed resolution produces no citation"],
|
||||
"assumptions": ["Source snapshots are immutable identifiers", "Exact excerpt offsets and text are supplied by a connector later", "Unknown/stale/conflicted evidence stays visible"],
|
||||
"acceptance_scenarios": ["Matching source chain resolves", "Wrong source/snapshot/excerpt is rejected", "Invalid offsets are rejected", "Citation preserves stale/conflict/assumed state", "No fabricated excerpt can resolve"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "citation resolver tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000111",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "aec63de",
|
||||
"risk_tier": "E",
|
||||
"intent": "Record read-only production hardware and model inventory evidence for WP-200 without changing model bindings or deployment.",
|
||||
"requirement_ids": ["WP-200", "Hardware/model inventory", "GPU/VRAM and protected runtime evidence"],
|
||||
"allowed_change_scope": {"modules": ["model-qualification-evidence"], "paths": ["docs/evidence/WP-200-HARDWARE-MODEL-INVENTORY-2026-08-22.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000111-hardware-model-inventory-evidence.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["WP-200 audit requirements", "Production docker-prod read-only inventory"], "may_change": []},
|
||||
"semantic_owner": "model-qualification-evidence",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["unqualified inventory -> evidence recorded -> qualification decision pending"],
|
||||
"side_effects_and_privileges": ["Read-only SSH/docker inspection only; no model activation, deployment, credential access or production mutation"],
|
||||
"persistence_and_migrations": ["Evidence document only; no database changes"],
|
||||
"failure_retry_restart": ["Missing GPU/model evidence remains UNKNOWN and blocks heavy qualification"],
|
||||
"assumptions": ["docker-prod/docker.cin.su is the only production target", "GPU absence on this host does not prove absence on every external host", "Model qualification requires separate holdout/latency/license evidence"],
|
||||
"acceptance_scenarios": ["CPU/RAM/storage/network inventory recorded", "GPU/VRAM/runtime evidence recorded", "Healthy cognitive/lifeboat containers identified without secret output", "Heavy model remains unqualified until GPU/license evidence exists"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "production read-only inventory commands"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000112",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "93bc4a0",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add an immutable model qualification manifest contract and validation for holdout, latency, resource, license and restart evidence without activating a model.",
|
||||
"requirement_ids": ["WP-201", "Qualification evidence manifest", "No activation from model name alone"],
|
||||
"allowed_change_scope": {"modules": ["capability-model-qualification"], "paths": ["contracts/capability/**", "tests/model_qualification/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000112-model-qualification-manifest.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["Existing Capability model descriptor/evaluation types", "WP-201 audit acceptance"], "may_change": ["Capability qualification evidence manifest contract"]},
|
||||
"semantic_owner": "capability-model-qualification",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["candidate model -> measured -> evidence complete/incomplete -> qualification decision pending"],
|
||||
"side_effects_and_privileges": ["Pure validation; no provider call, model activation, deployment, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Incomplete or stale manifest remains unqualified; rerun creates a new manifest revision"],
|
||||
"assumptions": ["A model digest and provider revision identify the tested artifact", "License/data policy evidence is explicit", "Qualification is separate from activation"],
|
||||
"acceptance_scenarios": ["Manifest binds model revision and digest", "Holdout corpus and all required dimensions are recorded", "Latency/concurrency/resource/restart evidence is present", "License and data policy are present", "Incomplete manifest cannot claim validated"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "model qualification manifest tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000113",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "1f01b70",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add a deterministic qualification gate that maps complete supported evidence to VALIDATED and incomplete/unsupported evidence to REJECTED without changing activation.",
|
||||
"requirement_ids": ["WP-201", "Qualification decision", "GPU/resource evidence cannot be inferred"],
|
||||
"allowed_change_scope": {"modules": ["capability-model-qualification"], "paths": ["contracts/capability/**", "tests/model_qualification/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000113-model-qualification-gate.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["QualificationManifest from DEV-000112", "Existing QualificationState and EvaluationResult values"], "may_change": ["Qualification gate projection"]},
|
||||
"semantic_owner": "capability-model-qualification",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["complete supported manifest -> VALIDATED", "incomplete/unsupported/unknown manifest -> REJECTED"],
|
||||
"side_effects_and_privileges": ["Pure validation; no binding activation, provider call, deployment, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Gate is deterministic; a new manifest revision is required after new measurements"],
|
||||
"assumptions": ["Qualification and activation remain separate lifecycle decisions", "All required dimensions are mandatory", "Model names never substitute for evidence"],
|
||||
"acceptance_scenarios": ["All supported measurements produce VALIDATED", "Unsupported resource/restart result produces REJECTED", "Unknown measurement produces REJECTED", "Gate does not mutate binding activation"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "qualification gate tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000114",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "a65ee37",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add deterministic AgentRun liveness assessment for stale-run recovery without requeueing or mutating production records.",
|
||||
"requirement_ids": ["WP-302", "Heartbeat timeout", "Typed stale evidence"],
|
||||
"allowed_change_scope": {"modules": ["work-recovery"], "paths": ["modules/work/domain/**", "contracts/work/**", "tests/work_recovery/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000114-run-liveness-assessment.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["Existing Work AgentRun lifecycle", "ADR-0017 delegation ownership"], "may_change": ["Work recovery liveness projection"]},
|
||||
"semantic_owner": "work-recovery",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["run timestamp evidence -> LIVE/STALE/INVALID", "stale evidence -> recovery decision pending"],
|
||||
"side_effects_and_privileges": ["Pure assessment; no requeue, resume, cancellation, persistence or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Clock/invalid timestamps produce INVALID rather than stale or healthy"],
|
||||
"assumptions": ["UpdatedAt is the latest durable heartbeat/checkpoint projection available", "Recovery policy chooses the next action separately", "RUNNING alone is not proof of liveness"],
|
||||
"acceptance_scenarios": ["Recent run is LIVE", "Old running run is STALE with age", "Completed/blocked run is not stale", "Invalid clock/timestamp is INVALID", "No production state changes"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "run liveness tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000115",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "e83cf35",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add a deterministic cognitive admission policy for concurrency, accepted/hour, deadline and input/output budgets without changing provider routing or bindings.",
|
||||
"requirement_ids": ["WP-203", "Bounded concurrency/rate limits", "Explainable admission decision"],
|
||||
"allowed_change_scope": {"modules": ["cognitive-admission"], "paths": ["contracts/capability/**", "modules/capability/domain/**", "tests/cognitive_admission/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000115-cognitive-admission-control.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["Existing cognitive capability registry and LimitPolicyRef", "ADR-0006 bounded advisory boundary"], "may_change": ["Typed admission request/policy/decision contract"]},
|
||||
"semantic_owner": "capability-cognitive-admission",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["request + policy + observed counters -> admitted or typed rejection"],
|
||||
"side_effects_and_privileges": ["Pure policy evaluation; no provider call, queue, persistence, binding mutation or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Rejected requests carry a reason; retry requires a fresh policy evaluation"],
|
||||
"assumptions": ["Counters are supplied by a later durable admission implementation", "Policy does not choose or activate providers", "Deadline and budgets are request requirements"],
|
||||
"acceptance_scenarios": ["Concurrency limit rejects excess request", "Accepted/hour limit rejects excess request", "Deadline and byte budget reject invalid request", "Admitted decision exposes reason and policy reference", "Simple/normal/heavy complexity remains a routing input, not authority"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "admission policy tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000116",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "cb92f95",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add typed durable-agent lease and fencing-token transitions for worker start/heartbeat without implementing a dispatcher or persistence adapter.",
|
||||
"requirement_ids": ["WP-301", "Worker lease confirms actual start", "Heartbeat fencing"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["contracts/work/**", "modules/work/domain/**", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000116-agent-lease-fencing.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 delegation/run ownership", "Proposed ADR-0020 Agent Execution Plane", "Existing Work AgentRun identity"], "may_change": ["Work agent lease/fencing contract"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["dispatch -> lease acquired -> heartbeat renewed", "wrong worker/token/expired lease -> rejected callback"],
|
||||
"side_effects_and_privileges": ["Pure transition helper; no queue, persistence, worker process, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Fencing token changes on a new lease; late old-token heartbeat is rejected"],
|
||||
"assumptions": ["A durable repository will persist the lease atomically later", "Lease acquisition is separate from process-local execution", "Worker identity is authenticated by a later adapter boundary"],
|
||||
"acceptance_scenarios": ["Valid worker acquires lease", "Matching worker renews before expiry", "Wrong worker/token rejected", "Expired lease cannot renew", "New token fences old run"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "agent lease tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000117",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "9766e53",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add typed callback envelope validation for authenticated worker identity, fencing token, monotonic sequence and lease freshness.",
|
||||
"requirement_ids": ["WP-301", "Callback auth binding", "Forged/out-of-order result rejection"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["contracts/work/**", "modules/work/domain/**", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000117-agent-callback-envelope.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["AgentLease/fencing contract from DEV-000116", "ADR-0020 callback ordering boundary"], "may_change": ["Agent callback envelope contract"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["lease + callback envelope -> accepted callback", "wrong identity/token/sequence/expiry -> rejected callback"],
|
||||
"side_effects_and_privileges": ["Pure validation; no transport, persistence, result mutation or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Duplicate and late callbacks are rejected by sequence/fencing; accepted sequence is persisted later by adapter"],
|
||||
"assumptions": ["Transport authenticates worker identity before this validation", "Sequence is monotonic within a run and lease token", "Result verification remains a separate step"],
|
||||
"acceptance_scenarios": ["Matching heartbeat accepted", "Wrong worker/token rejected", "Duplicate/out-of-order sequence rejected", "Expired lease rejected", "Callback run mismatch rejected"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "agent callback tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000118",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "a6eb4ab",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add typed artifact attestation for agent-produced evidence with digest, source/worktree, producer run and test provenance.",
|
||||
"requirement_ids": ["WP-301", "Artifact attestation", "Independent evidence refs"],
|
||||
"allowed_change_scope": {"modules": ["artifact-agent-evidence"], "paths": ["contracts/artifact/**", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000118-agent-artifact-attestation.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 Artifact ownership", "ADR-0020 agent evidence attestation", "Existing AgentRun identity"], "may_change": ["Artifact agent attestation contract"]},
|
||||
"semantic_owner": "artifact-agent-evidence",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["agent output -> attested artifact evidence", "missing provenance -> non-attested result"],
|
||||
"side_effects_and_privileges": ["Pure validation; no filesystem access, merge, deployment, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Attestation is immutable; changed digest/source requires a new attestation"],
|
||||
"assumptions": ["Artifact storage computes or verifies digest later", "Producer run and source revision are durable refs", "Verifier remains distinct from producer"],
|
||||
"acceptance_scenarios": ["Complete attestation validates", "Missing digest/source/worktree/test evidence rejects", "Attestation binds to one producer run", "Changed artifact requires a new attestation"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "artifact attestation tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000119",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "02b472b",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add independent verification contract requiring a distinct verifier identity/run and attested artifact evidence before Work completion.",
|
||||
"requirement_ids": ["WP-301", "Independent verifier", "Work completion gate"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-verification"], "paths": ["contracts/work/**", "modules/work/domain/**", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000119-independent-verification-contract.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 verification separation", "ADR-0020 independent verifier", "Agent artifact attestation from DEV-000118"], "may_change": ["Work independent verification contract"]},
|
||||
"semantic_owner": "work-agent-verification",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["producer result + attestation -> verifier assessment -> verified/rejected"],
|
||||
"side_effects_and_privileges": ["Pure validation; no merge, deployment, binding activation, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Rejected verification preserves producer evidence; a new verifier run is required"],
|
||||
"assumptions": ["Verifier identity and run are authenticated later", "Producer and verifier cannot be the same run", "Artifact evidence is immutable"],
|
||||
"acceptance_scenarios": ["Distinct verifier validates attested artifact", "Same run is rejected", "Missing artifact/test evidence is rejected", "Rejected verification does not erase producer result"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "independent verification tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000120",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "04dad79",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add typed durable dispatch intent and idempotency contract for agent runs without implementing a dispatcher, queue or worker process.",
|
||||
"requirement_ids": ["WP-301", "Durable dispatch", "Duplicate dispatch prevention"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["contracts/work/**", "modules/work/domain/**", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000120-agent-dispatch-intent.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["ADR-0017 delegation/run ownership", "ADR-0020 dispatch idempotency", "Existing Capability implementation identity"], "may_change": ["Work dispatch intent contract"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["dispatch intent -> accepted once by idempotency key -> lease acquisition", "same idempotency key -> same dispatch identity"],
|
||||
"side_effects_and_privileges": ["Pure validation; no queue, process start, persistence adapter, credential access or external mutation"],
|
||||
"persistence_and_migrations": ["No schema or migration changes"],
|
||||
"failure_retry_restart": ["Retry reuses intent identity only when the durable store confirms the same payload; changed payload requires a new key"],
|
||||
"assumptions": ["Dispatcher persists intent atomically later", "Exact source/worktree is resolved before worker start", "Verification plan is part of dispatch provenance"],
|
||||
"acceptance_scenarios": ["Complete dispatch validates", "Missing idempotency/source/worktree/verification plan rejects", "Payload identity is explicit", "Worker implementation is typed and not inferred from free text"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "dispatch intent tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000121",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "f792d20",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add work-owned durable persistence schema for dispatch intents, leases and callback sequence heads, with repository write boundaries.",
|
||||
"requirement_ids": ["WP-301", "Durable dispatch persistence", "Atomic lease/fencing state"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "work-storage"], "paths": ["database/migrations/029_work_agent_execution_persistence.sql", "adapters/postgres/work_repository.go", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000121-agent-execution-persistence.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", "adapters/continuity*", "adapters/capability*", ".gitea/**"]},
|
||||
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 1},
|
||||
"contracts": {"consume": ["DEV-000120 dispatch intent", "DEV-000116 lease/fencing", "DEV-000117 callback sequence"], "may_change": ["Work-owned persistence schema and repository methods"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["work.agent_dispatch_intents", "work.agent_leases", "work.agent_callback_heads"],
|
||||
"state_transitions": ["dispatch intent persisted idempotently", "lease acquired/renewed with fencing token", "callback sequence advanced atomically"],
|
||||
"side_effects_and_privileges": ["Work schema writes only through Work repository; no cross-owner SQL"],
|
||||
"persistence_and_migrations": ["Append-only migration 029; work_role only"],
|
||||
"failure_retry_restart": ["Duplicate idempotency key is reconciled; lease conflict is rejected; callback sequence uses row lock/conditional update"],
|
||||
"assumptions": ["Migration is reviewed and applied before runtime enablement", "Production deployment is a later canary task", "No secrets are stored in dispatch payload"],
|
||||
"acceptance_scenarios": ["Duplicate dispatch cannot create a second intent", "Expired/wrong lease cannot advance", "Out-of-order callback sequence is rejected", "Work owner boundaries remain intact"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "migration/repository tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MIGRATION_DECISION", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000122",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "8081b4d",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement an opt-in deterministic dry-run dispatcher and fake worker cycle over the Agent Execution Plane contracts.",
|
||||
"requirement_ids": ["WP-301", "Dry-run dispatcher", "Deterministic fake worker"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["contracts/work/dry_run_dispatch.go", "modules/work/domain/dry_run_dispatch.go", "tests/agent_execution/dry_run_dispatch_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000122-agent-dry-run-dispatch.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", "adapters/**", "database/**", ".gitea/**"]},
|
||||
"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": ["DEV-000120 dispatch intent", "DEV-000116 lease/fencing", "DEV-000117 callback sequence"], "may_change": ["Dry-run dispatch result value" ]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["DryRunDispatch value; no production state"],
|
||||
"state_transitions": ["validated intent -> deterministic lease -> heartbeat callback -> result callback"],
|
||||
"side_effects_and_privileges": ["Pure domain construction only; no external mutation"],
|
||||
"persistence_and_migrations": ["Uses DEV-000121 persistence contract but does not apply migration"],
|
||||
"failure_retry_restart": ["Invalid intent, worker, token, time or TTL is rejected"],
|
||||
"assumptions": ["Runtime dispatcher and production worker remain separate follow-up work"],
|
||||
"acceptance_scenarios": ["Same input produces byte-for-byte equivalent typed output", "Invalid intent is rejected", "Callbacks are ordered and inside lease window"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000123",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "fa62322",
|
||||
"risk_tier": "E",
|
||||
"intent": "Connect the opt-in deterministic dry-run dispatcher to a narrow Work-owned execution persistence port and application coordinator.",
|
||||
"requirement_ids": ["WP-301", "Work execution port", "Dry-run orchestration"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["modules/work/ports/execution.go", "modules/work/application/dry_run_dispatch.go", "tests/agent_execution/dry_run_orchestration_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000123-agent-dry-run-orchestration.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", "adapters/**", "database/**", ".gitea/**"]},
|
||||
"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": ["DEV-000121 persistence methods", "DEV-000122 dry-run dispatch"], "may_change": ["Work execution port and application coordinator"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["Persistence remains owned by Work repository"],
|
||||
"state_transitions": ["dispatch intent -> lease -> ordered callbacks"],
|
||||
"side_effects_and_privileges": ["Explicit Work-owned port only; no runtime entrypoint or production enablement"],
|
||||
"persistence_and_migrations": ["Reuses DEV-000121 schema; no migration"],
|
||||
"failure_retry_restart": ["Idempotent repository methods make retries safe; partial failure is returned to caller"],
|
||||
"assumptions": ["Production dispatcher remains disabled until a separate runtime canary task"],
|
||||
"acceptance_scenarios": ["All persistence operations are invoked in deterministic order", "Invalid dispatch never reaches the store"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000124",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "7f82240",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add an explicit opt-in canary gate for the dry-run agent dispatcher with implementation allowlist and bounded dispatch count.",
|
||||
"requirement_ids": ["WP-301", "Opt-in canary", "Bounded dispatch"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["modules/work/application/canary.go", "tests/agent_execution/canary_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000124-agent-canary-gate.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", "adapters/**", "database/**", ".gitea/**"]},
|
||||
"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": ["DEV-000123 dry-run orchestration"], "may_change": ["Canary configuration and gate errors"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["Canary configuration is caller-owned; dispatch state remains Work-owned"],
|
||||
"state_transitions": ["disabled/unauthorized/limited -> rejected; enabled and allowed -> dry-run orchestration"],
|
||||
"side_effects_and_privileges": ["No side effect before all canary gates pass"],
|
||||
"persistence_and_migrations": ["Reuses existing persistence; no migration"],
|
||||
"failure_retry_restart": ["Caller receives typed gate failure; retries remain bounded by used dispatch count"],
|
||||
"assumptions": ["Runtime wiring and production configuration are separate follow-up work"],
|
||||
"acceptance_scenarios": ["Disabled canary writes nothing", "Wrong implementation writes nothing", "Limit is enforced", "Allowed canary persists four records"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000125",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "d2338b1",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add a manually invoked CLI harness for the opt-in deterministic agent canary using an in-memory store.",
|
||||
"requirement_ids": ["WP-301", "Manual canary entrypoint", "Dry-run verification"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["cmd/agent-canary/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000125-agent-canary-cli.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "runtime/**", "adapters/**", "database/**", ".gitea/**"]},
|
||||
"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": ["DEV-000124 canary gate"], "may_change": ["Manual CLI entrypoint"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["No durable state; in-memory canary evidence only"],
|
||||
"state_transitions": ["CLI flags -> canary gate -> deterministic callbacks -> JSON summary"],
|
||||
"side_effects_and_privileges": ["No network, database, deployment or production mutation"],
|
||||
"persistence_and_migrations": ["Explicitly does not use production repository"],
|
||||
"failure_retry_restart": ["Disabled or unauthorized invocation exits nonzero without writes"],
|
||||
"assumptions": ["Real WorkRepository wiring requires a separate approved runtime task"],
|
||||
"acceptance_scenarios": ["Default invocation is rejected", "Explicit enablement produces JSON summary", "No secrets or external connections are required"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/agent-canary", "go run ./cmd/agent-canary -enable-canary", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000126",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "c30e0c7",
|
||||
"risk_tier": "E",
|
||||
"intent": "Wire the manual agent canary CLI to the existing Work PostgreSQL repository behind an explicit flag while preserving in-memory default behavior.",
|
||||
"requirement_ids": ["WP-301", "Repository canary wiring", "Credential-safe adapter use"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "work-storage"], "paths": ["cmd/agent-canary/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000126-agent-canary-repository.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "runtime/**", "database/**", ".gitea/**"]},
|
||||
"allowed_architecture_delta": {"new_cross_module_dependencies": 1, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
|
||||
"contracts": {"consume": ["DEV-000125 manual canary CLI", "DEV-000121 WorkRepository methods"], "may_change": ["Manual CLI storage selection"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["WorkRepository remains canonical for postgres mode"],
|
||||
"state_transitions": ["CLI -postgres -> OpenWork -> canary coordinator -> repository writes"],
|
||||
"side_effects_and_privileges": ["Only explicit -postgres mode connects; credentials come from existing environment variables and are never printed"],
|
||||
"persistence_and_migrations": ["Requires migration 029 to be applied separately; this task does not apply migrations"],
|
||||
"failure_retry_restart": ["Connection or missing migration errors are returned; no automatic retry or deployment"],
|
||||
"assumptions": ["Default mode remains in-memory and production entrypoints are unchanged"],
|
||||
"acceptance_scenarios": ["Default mode remains deterministic", "Postgres mode uses Work repository through ExecutionStore", "No secret values appear in output"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/agent-canary", "go run ./cmd/agent-canary -enable-canary", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MIGRATION_DECISION", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000127",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "8e26826",
|
||||
"risk_tier": "E",
|
||||
"intent": "Apply the already reviewed additive Work migration 029 on the canonical docker-prod PostgreSQL instance for controlled canary preparation.",
|
||||
"requirement_ids": ["WP-301", "Migration canary", "Durable execution readiness"],
|
||||
"allowed_change_scope": {"modules": ["work-storage"], "paths": ["dev/ACTIVE_TASK.json", "dev/tasks/DEV-000127-apply-agent-execution-migration.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["DEV-000121 migration 029", "DEV-000126 repository wiring"], "may_change": ["Canonical production Work schema state"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["docker-prod assistant PostgreSQL work schema"],
|
||||
"state_transitions": ["tables absent -> tables present and verified"],
|
||||
"side_effects_and_privileges": ["One additive transactional migration through existing bootstrap/superuser path; no secrets printed"],
|
||||
"persistence_and_migrations": ["Apply database/migrations/029_work_agent_execution_persistence.sql exactly once"],
|
||||
"failure_retry_restart": ["ON CONFLICT-safe DDL; ON_ERROR_STOP; verify before any retry"],
|
||||
"assumptions": ["docker-prod is the canonical target", "migration file is already committed and reviewed", "runtime remains disabled until a later canary"],
|
||||
"acceptance_scenarios": ["All three Work tables exist after apply", "No non-Work schema changes", "Existing production containers remain running"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "pre-apply table absence evidence", "migration apply exit 0", "post-apply table existence verification", "docker-prod container health read-only check", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_MIGRATION_DECISION", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000128",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "790fd7e",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add an explicitly profiled production compose service for the manual agent canary container.",
|
||||
"requirement_ids": ["WP-301", "Canary container profile", "Disabled-by-default runtime"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "production-compose"], "paths": ["deploy/docker-compose.prod.yml", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000128-agent-canary-compose-profile.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "runtime/**", "adapters/**", ".gitea/**"]},
|
||||
"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": ["DEV-000126 agent-canary postgres mode", "DEV-000127 migration 029"], "may_change": ["Production compose canary profile"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["No new state; uses Work schema"],
|
||||
"state_transitions": ["profile absent -> explicit profile service available; default stack unchanged"],
|
||||
"side_effects_and_privileges": ["Credential environment is injected by deployment system; service is disabled unless profile is explicitly selected"],
|
||||
"persistence_and_migrations": ["Requires migration 029 already present; no migration in this task"],
|
||||
"failure_retry_restart": ["restart=no; one bounded canary invocation"],
|
||||
"assumptions": ["Deployment of this profile is a separate approved canary action", "No normal service depends on agent-canary"],
|
||||
"acceptance_scenarios": ["Compose validates", "Default config does not start agent-canary", "Profile builds target cmd/agent-canary", "No secret literal is added"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "docker compose -f deploy/docker-compose.prod.yml config --quiet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_DEPLOYMENT_DECISION", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Governance lineage decision — 2026-08-22
|
||||
|
||||
Status: accepted as part of DEV-000099.
|
||||
|
||||
## DEV-000073 resolution
|
||||
|
||||
The repository history contains more than one DEV-000073 packet variant: a
|
||||
production-deployment packet and an earlier durable-bootstrap work-in-progress.
|
||||
Neither is the current active task, and the current main lineage does not
|
||||
contain one uniquely authoritative DEV-000073 packet. References such as
|
||||
`DEV-000073-baseline` in later historical packets are retained as evidence of
|
||||
their implementation baseline, not treated as an accepted current architecture
|
||||
revision or active work pointer.
|
||||
|
||||
The accepted architecture authority for the current project is the accepted
|
||||
ADR chain, currently ADR-0017 and ADR-0018. A future task that needs the
|
||||
DEV-000073 deployment lineage must name an exact packet/revision and establish
|
||||
its ancestry before activation. No historical checkpoint is rewritten.
|
||||
|
||||
## Current governance authority
|
||||
|
||||
`dev/ACTIVE_TASK.json` selects exactly one current packet. The packet declares
|
||||
its own path and the active pointer in scope. `architecture-registry.yaml` is
|
||||
the human-maintained registry mirror; `architecture-registry.json` is the
|
||||
machine-readable equivalent consumed by `devctl`; `architecture-registry.md`
|
||||
is the cold-agent-readable projection. Their semantic content is synchronized
|
||||
for the current ownership/topology baseline.
|
||||
@@ -1,5 +1,18 @@
|
||||
# Assistant project status
|
||||
|
||||
Audit date: 2026-08-22. Active implementation: DEV-000099 revision 1.
|
||||
|
||||
## Current governance checkpoint
|
||||
|
||||
- `DEV_000099_STATUS = COMPLETED`
|
||||
- `LAST_COMPLETED = DEV-000098 Governance Sentinel V2: full task-packet validation, scope-conflict rejection, architecture/base checks, required-test enforcement and independent verifier`
|
||||
- `ACTIVE_TASK = dev/tasks/DEV-000099-governance-consolidation.json revision 1`
|
||||
- `LINEAGE_DECISION = docs/GOVERNANCE_LINEAGE_DECISION_2026-08-22.md; DEV-000073 historical variants are preserved as evidence and are not current authority`
|
||||
- `REGISTRY_STATUS = YAML and JSON semantic mirrors agree; human registry projection synchronized with ADR-0017 ownership and current protected paths`
|
||||
- `PRODUCTION_DOMAIN = docker.cin.su; shared docker-test remains prohibited`
|
||||
- `RUNTIME_CHANGE = none; this governance consolidation changes repository authority/projections only`
|
||||
- `NEXT_RECOMMENDED_STEP = create a separately scoped WP-002 production recovery task packet; production changes remain pending that packet`
|
||||
|
||||
Audit date: 2026-08-17. Active implementation: DEV-000072 revision 1.
|
||||
|
||||
## DEV-000072 checkpoint
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# WP-200 Hardware and model inventory evidence
|
||||
|
||||
Task: DEV-000111
|
||||
Evidence date: 2026-08-22
|
||||
Target: `docker-prod` / `docker.cin.su`
|
||||
|
||||
## Host evidence
|
||||
|
||||
Read-only `ssh docker` inspection returned:
|
||||
|
||||
- host: `docker`
|
||||
- CPU: `128` logical processors
|
||||
- memory: `251 GiB` total, `203 GiB` available at inspection time
|
||||
- swap: `8 GiB`, `256 KiB` used
|
||||
- root storage: `1.6T` total, `359G` used, `1.2T` available, `24%`
|
||||
- `nvidia-smi`: unavailable
|
||||
- Docker server: `29.4.0`, `128` CPUs, approximately `252 GiB` Docker memory
|
||||
- Docker runtimes: `runc`/`containerd`; no NVIDIA runtime reported
|
||||
|
||||
## Runtime evidence
|
||||
|
||||
Read-only `docker --context docker-prod` inspection found:
|
||||
|
||||
- `assistant-cognitive-worker-1`: production revision `b439a813...`, `/healthz` returned `status=ok`
|
||||
- `assistant-cognitive-lifeboat-1`: `ghcr.io/ggml-org/llama.cpp:server`, Docker health `healthy`
|
||||
- `llm-local-api`: `ghcr.io/ggml-org/llama.cpp:server`, Docker health `healthy`
|
||||
- no model activation, routing change or deployment was performed by this inventory task
|
||||
|
||||
## Qualification conclusion
|
||||
|
||||
This host has no verified GPU/VRAM capability in the inspected runtime. CPU
|
||||
and lifeboat health are evidence of availability, not model qualification.
|
||||
WP-201 still requires immutable model digests, holdout quality tests,
|
||||
latency/concurrency measurements, license/data-policy evidence and restart
|
||||
behavior. Heavy-class activation remains a qualification decision, not an
|
||||
inventory inference.
|
||||
|
||||
## Evidence limitations
|
||||
|
||||
This record does not establish the topology of any separate GPU host, does not
|
||||
read secrets, and does not claim that a model is qualified from its image name
|
||||
or health endpoint alone.
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# Proposal ADR-0019 — Epistemics Truth V1
|
||||
|
||||
Status: Proposed
|
||||
Task: DEV-000103
|
||||
|
||||
## Problem and evidence
|
||||
|
||||
The audit found that Domovoy has experience and reflection evidence, but no
|
||||
small, explicit truth model for observations, claims, source provenance,
|
||||
freshness or conflicts. Without that boundary, research, context compilation
|
||||
and future learning could silently turn an unverified statement into durable
|
||||
fact. The proposal is architecture-only: it does not add tables, runtime
|
||||
handlers, connectors or belief mutation.
|
||||
|
||||
## Decision proposed
|
||||
|
||||
1. Do not create a new canonical owner. Epistemics owns truth evidence and
|
||||
rebuildable truth projections; Interaction, Situation, Work, Capability and
|
||||
Artifact retain ownership of their canonical records.
|
||||
2. Define six typed records, with immutable identity and append-only history:
|
||||
`Observation`, `Claim`, `BeliefAssessment`, `Source` and `ProvenanceEdge`,
|
||||
plus a typed `VerificationState` value. Records reference other owners by
|
||||
typed IDs and never copy their canonical payloads.
|
||||
3. An `Observation` is a bounded acquired datum with acquisition time,
|
||||
source snapshot reference, excerpt/hash reference, collector identity and
|
||||
privacy/egress classification. It is evidence, not truth.
|
||||
4. A `Claim` is a proposition with explicit subject/predicate/object or
|
||||
proposition payload, scope, language, creator and claim status. It is not a
|
||||
fact merely because it is repeated or model-generated.
|
||||
5. A `BeliefAssessment` is a revisable, scoped assessment over one claim,
|
||||
carrying verification state, confidence band, freshness result, coverage
|
||||
result, conflict set and supporting/contradicting provenance edges.
|
||||
6. `Source` identifies the origin and retrieval policy; a source snapshot is
|
||||
the immutable artifact used for a particular observation. Source identity
|
||||
alone is never a citation.
|
||||
7. `ProvenanceEdge` records typed support, contradiction, derivation,
|
||||
correction or supersession between evidence and claims. A projection may
|
||||
be rebuilt from these edges and append-only records.
|
||||
8. Verification states are explicit: `UNVERIFIED`, `SUPPORTED`,
|
||||
`CORROBORATED`, `CONFLICTED`, `STALE`, `RETRACTED` and `UNKNOWN`.
|
||||
`UNKNOWN` and `ASSUMED` remain visible and cannot be promoted implicitly.
|
||||
9. Freshness and coverage are evidence dimensions, not confidence shortcuts.
|
||||
A stale or incomplete source may support a claim only with that limitation
|
||||
attached. Conflicts are retained and surfaced; they are not resolved by
|
||||
latest-write-wins.
|
||||
10. Privacy, authorization and egress filtering happen before observation
|
||||
persistence or external citation. Secrets and unapproved personal data
|
||||
are fail-closed and cannot become source snapshots.
|
||||
|
||||
## Alternatives considered and rationale
|
||||
|
||||
- Store truth directly in Situation or Interaction: rejected because those
|
||||
owners would become provenance/verification authorities and would mix
|
||||
canonical operational state with revisable evidence.
|
||||
- Use a single confidence score or latest-write-wins: rejected because it
|
||||
hides freshness, incomplete coverage and contradiction, and cannot explain
|
||||
why an assessment changed.
|
||||
- Introduce a universal ontology engine: rejected because it expands scope,
|
||||
makes ownership ambiguous and is unnecessary for the bounded claims needed
|
||||
by the research and context work packages.
|
||||
|
||||
The proposed small typed set keeps evidence historical, makes uncertainty
|
||||
visible and allows projections to be rebuilt without granting models or
|
||||
connectors canonical authority.
|
||||
|
||||
## Lifecycle and correction
|
||||
|
||||
Observation and source snapshots are append-only and retention-governed.
|
||||
Claims and assessments are superseded or retracted by new typed records;
|
||||
history is never deleted to hide an earlier conclusion. Human correction is
|
||||
strong evidence with explicit provenance, but does not become a permanent
|
||||
rule without a later assessment. Rebuildable projections may be compacted
|
||||
only while preserving their source record references and retention policy.
|
||||
|
||||
## Boundaries and non-goals
|
||||
|
||||
- No universal ontology, generic `BeliefManager` or `map[string]any` truth
|
||||
store.
|
||||
- No model output directly mutates claims, assessments, authority or source
|
||||
snapshots.
|
||||
- No web/repository connector, citation resolver, migration or UI change is
|
||||
included in this proposal.
|
||||
- External research remains read-only until a separate WP-102 contract and
|
||||
connector task is accepted.
|
||||
|
||||
## Consequences
|
||||
|
||||
Truthful product surfaces can distinguish evidence, assessment, uncertainty,
|
||||
staleness and conflict. Research and context compilation gain stable
|
||||
provenance inputs, while implementation must carry typed references and
|
||||
explicit freshness/coverage results. The proposal adds review and storage
|
||||
discipline but intentionally provides no immediate runtime capability.
|
||||
|
||||
## Migration and rollback
|
||||
|
||||
There is no migration in this proposal. If accepted, implementation must add
|
||||
append-only records and projections behind owner-local interfaces, with a
|
||||
rebuild harness and a feature flag/canary before any existing response path
|
||||
consumes assessments. Rejection or rollback disables the projection consumer;
|
||||
historical source evidence remains intact and no canonical operational state
|
||||
is mutated.
|
||||
|
||||
## Acceptance and revisit triggers
|
||||
|
||||
The proposal is ready for acceptance when owner boundaries, identifiers,
|
||||
retention/privacy rules, correction/supersession lifecycle, rebuildable
|
||||
projections and conflict semantics have implementation-level contract tests
|
||||
specified without introducing a new owner. Revisit if a source requires
|
||||
write access, if retention law changes, if a second canonical owner is
|
||||
proposed, or if confidence is used as an authority/permission decision.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Proposal ADR-0020 — Agent Execution Plane V1
|
||||
|
||||
Status: Proposed
|
||||
Task: DEV-000104
|
||||
|
||||
## Problem and evidence
|
||||
|
||||
ADR-0017 defines durable delegation semantics, but the audit found no
|
||||
automatic dispatcher/worker plane, lease/heartbeat lifecycle, callback
|
||||
authentication or independent verification boundary. A worker must be able
|
||||
to stop, restart and resume without turning a missing callback into success or
|
||||
silently duplicating external work.
|
||||
|
||||
## Decision proposed
|
||||
|
||||
1. Keep `work` as the canonical owner of `Delegation`, `AgentRun`,
|
||||
`AgentCheckpoint`, `AgentResult` and verification gating. `capability`
|
||||
owns executor-neutral agent implementations; adapters own transport and
|
||||
process details.
|
||||
2. Every dispatch has a stable `dispatch_id`, delegation id, run id,
|
||||
implementation id, exact source/worktree identity, bounded instruction
|
||||
artifact reference, requested capability set, authority/credential refs,
|
||||
deadline and idempotency key. The derived instruction must preserve
|
||||
OriginalHumanIntent, inferred goal, added context and verification plan as
|
||||
separate provenance fields.
|
||||
3. A worker must acquire a durable lease before execution. Lease ownership is
|
||||
a fencing token, not an in-memory mutex. Heartbeats extend the lease only
|
||||
for the same run and fencing token; an expired lease makes late callbacks
|
||||
non-authoritative.
|
||||
4. Dispatch and callback endpoints authenticate the worker identity and bind
|
||||
every message to delegation, run, dispatch, implementation and fencing
|
||||
token. Forged, replayed, out-of-order or duplicate callbacks are rejected
|
||||
or recorded as non-authoritative evidence. Idempotent acknowledgement is
|
||||
allowed only for the exact previously accepted event.
|
||||
5. Checkpoints and artifacts are content-addressed metadata owned by
|
||||
`artifact`, with typed refs from Work. Artifact attestation records source
|
||||
revision/worktree, producer, digest, test command/result and capture time;
|
||||
an un-attested path or log is not completion evidence.
|
||||
6. Cancellation is a durable Work decision. The dispatcher sends cancellation
|
||||
to the leased worker, records acknowledgement or timeout, and never erases
|
||||
history. A worker that cannot prove cancellation remains `UNKNOWN` or
|
||||
`RECOVERING` until reconciled.
|
||||
7. Retry is policy-driven and distinguishes retryable transport/worker loss,
|
||||
execution-window exhaustion, human blocker, invalid result and unknown
|
||||
external outcome. Retry creates a new run under the same delegation and
|
||||
preserves prior checkpoints/results; it never reuses an idempotency key for
|
||||
an unproven external mutation.
|
||||
8. A run result cannot complete Work directly. Independent verification must
|
||||
consume the declared artifacts and tests through a separate verifier
|
||||
identity and produce a typed verification result. Verification failure,
|
||||
missing evidence and verifier unavailability remain distinct states.
|
||||
9. Secrets are resolved only through approved `CredentialRef` boundaries and
|
||||
are not copied into instructions, callbacks, artifacts, logs or task
|
||||
packets. Worker startup has no ambient credential inheritance.
|
||||
10. The plane is durable and restartable: dispatch intent, lease, heartbeat,
|
||||
cancellation, checkpoint, result, verification and reconciliation events
|
||||
are persisted before their projections claim progress.
|
||||
|
||||
## Lifecycle proposal
|
||||
|
||||
Delegation remains the higher-level human/work intent. A run progresses through
|
||||
`DISPATCHED`, `LEASED`, `RUNNING`, `CHECKPOINTED`, `SUCCEEDED`, `FAILED`,
|
||||
`CANCEL_REQUESTED`, `CANCELLED`, `EXHAUSTED`, `WAITING_HUMAN`, `UNKNOWN` or
|
||||
`RECOVERING`, subject to explicit transition guards. `SUCCEEDED` means only
|
||||
that the worker returned an accepted result; Work completion additionally
|
||||
requires independent verification. Lease expiry and missing heartbeat never
|
||||
become success.
|
||||
|
||||
## Alternatives and rationale
|
||||
|
||||
- An in-memory queue or goroutine was rejected because it loses dispatch state
|
||||
on restart and violates durable timer/lease requirements.
|
||||
- A single generic `AgentManager` was rejected because it collapses Work,
|
||||
Capability, Artifact and Continuity ownership.
|
||||
- Trusting worker-reported success was rejected because the worker and
|
||||
verifier must remain separate authorities.
|
||||
- Reusing the same run on retry was rejected because it makes duplicate
|
||||
external effects and late callbacks indistinguishable.
|
||||
|
||||
## Boundaries, consequences and non-goals
|
||||
|
||||
No new canonical owner, provider, deployment authority, database migration,
|
||||
Codex-specific domain contract or automatic self-modification is proposed.
|
||||
The design enables restartable external work and truthful failure handling,
|
||||
but requires durable event ordering, fencing, artifact storage and verifier
|
||||
capacity before WP-301 can claim production readiness.
|
||||
|
||||
## Migration, rollback and revisit triggers
|
||||
|
||||
Implementation must begin behind an opt-in dispatch path with a dry-run
|
||||
verifier and deterministic fake worker. Existing delegation records remain
|
||||
readable; unknown legacy RUNNING records are classified by WP-302 rather than
|
||||
silently resumed. Rollback disables new dispatch and drains/recovers leases;
|
||||
history and artifacts remain. Revisit if a worker needs authority beyond its
|
||||
declared capability, if a second transport changes callback guarantees, or if
|
||||
artifact attestation cannot be made independent of the worker.
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Proposal ADR-0021 — Multi-Agent Work Graph V1
|
||||
|
||||
Status: Proposed
|
||||
Task: DEV-000105
|
||||
|
||||
## Problem and evidence
|
||||
|
||||
The audit found durable Work and Delegation records but no multi-agent DAG,
|
||||
fan-out/fan-in scheduler, budgets, leases, conflict resolution or independent
|
||||
verifier. Adding parallel agents without those controls would create duplicate
|
||||
work, unbounded cost, hidden disagreement and false completion.
|
||||
|
||||
## Decision proposed
|
||||
|
||||
1. `work` owns the durable graph: `WorkGraph`, `GraphRevision`, `WorkNode`,
|
||||
`NodeDependency`, `ResultCandidate`, `ConflictSet` and synthesis/verification
|
||||
gating. Capability remains the source of eligible agent implementations;
|
||||
Artifact owns result metadata; Continuity owns human attention.
|
||||
2. A graph is immutable by revision. Each revision contains typed nodes,
|
||||
dependency edges, requested capabilities, budgets, cancellation policy,
|
||||
synthesis policy and verifier requirements. Acyclicity and ownership are
|
||||
validated before dispatch; edits create a new revision rather than mutating
|
||||
a running graph.
|
||||
3. Node identity and idempotency are stable within a revision. Independent
|
||||
nodes may fan out only after graph admission checks. A dependent node is
|
||||
dispatchable only when all required prerequisites have accepted results.
|
||||
Partial results and failed nodes remain attributable and do not erase
|
||||
sibling results.
|
||||
4. Scheduling enforces durable per-goal, per-user, per-resource and per-graph
|
||||
budgets for concurrency, attempts, time, tokens and external mutation
|
||||
class. Backpressure and starvation detection are explicit; a role or agent
|
||||
cannot bypass a budget.
|
||||
5. Assignment uses the Agent Execution Plane lease/fencing rules. Pause,
|
||||
cancel, reprioritize and resume are durable graph decisions propagated to
|
||||
eligible nodes. Restart/replay rebuilds from graph revision and execution
|
||||
events, not process-local queues.
|
||||
6. Agent outputs are `ResultCandidate` records with author run, node, graph
|
||||
revision, artifact refs, evidence and claims. Conflicting candidates form a
|
||||
`ConflictSet`; dissent and unresolved conflict remain visible.
|
||||
7. Synthesis is a candidate, never an authority shortcut. A verifier with a
|
||||
distinct run/identity from the author consumes declared evidence and emits
|
||||
a typed verification result. Work completion requires the graph's verifier
|
||||
policy and does not occur from fan-in text alone.
|
||||
8. Specialized roles are capability profiles and evidence-based selection
|
||||
hints, not hardcoded authority classes. One agent may hold multiple roles;
|
||||
role selection never grants credentials or permission.
|
||||
|
||||
## Lifecycle and failure semantics
|
||||
|
||||
Graph states are `DRAFT`, `ADMITTED`, `RUNNING`, `PAUSED`, `CANCELLING`,
|
||||
`PARTIAL`, `VERIFYING`, `COMPLETED`, `FAILED`, `UNKNOWN` and `RECOVERING`.
|
||||
Node states distinguish `BLOCKED_PREREQUISITE`, `READY`, `LEASED`, `RUNNING`,
|
||||
`SUCCEEDED`, `FAILED`, `CANCELLED`, `UNKNOWN` and `RECOVERING`. A failed node
|
||||
may trigger policy-defined retry or human attention; it never deletes other
|
||||
node evidence. Unknown external outcomes block unsafe replay until reconciled.
|
||||
|
||||
## Alternatives and rationale
|
||||
|
||||
- A shared in-memory task queue was rejected because restart loses graph state
|
||||
and violates durable scheduling semantics.
|
||||
- A flat list of independent agents was rejected because dependencies,
|
||||
fan-in, cycle rejection and partial completion cannot be represented safely.
|
||||
- Majority vote without provenance was rejected because disagreement and
|
||||
source quality would be hidden.
|
||||
- Agent roles as privileged classes were rejected because capability evidence,
|
||||
not labels, must determine eligibility and authority remains separate.
|
||||
|
||||
## Consequences, migration and rollback
|
||||
|
||||
The graph enables bounded parallel work and attributable synthesis, at the
|
||||
cost of durable event ordering, budget accounting and verifier capacity. No
|
||||
runtime, schema or scheduler is included in this proposal. Implementation
|
||||
must begin with a deterministic fake graph harness, cycle/budget/conflict
|
||||
tests and opt-in dispatch. Rollback disables new graph admission and drains
|
||||
or recovers active leases without deleting graph history or artifacts.
|
||||
|
||||
Revisit if a graph node needs authority outside its capability, if cross-owner
|
||||
joins become necessary, if synthesis cannot preserve dissent, or if the
|
||||
verifier cannot be made independent of the author run.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Proposal ADR-0022 — Governed Self-Development V1
|
||||
|
||||
Status: Proposed
|
||||
Task: DEV-000106
|
||||
|
||||
## Problem and evidence
|
||||
|
||||
The audit found ReflectionCandidates and capability evidence, but no safe
|
||||
pipeline for turning repeated evidence into a reviewed change. Direct
|
||||
self-editing would let model output alter source, skills, authority or
|
||||
production without provenance, evaluation or rollback.
|
||||
|
||||
## Decision proposed
|
||||
|
||||
1. Introduce a governed development control plane. `ExtensionProposal` is an
|
||||
evidence-bearing request that moves through review, implementation and
|
||||
promotion stages.
|
||||
2. A proposal must reference repeated or corroborated evidence, desired
|
||||
outcome, smallest change layer, affected owners/contracts, assumptions,
|
||||
alternatives, risk tier, evaluation plan, rollback and emergency-stop
|
||||
conditions. One accidental failure is insufficient; duplicate evidence is
|
||||
deduplicated without deleting its history.
|
||||
3. Candidate artifacts are immutable, content-addressed and lineage-linked to
|
||||
the proposal, task packet, source revision, generator/author identity,
|
||||
tests, evaluations and environment. Candidate code, skill, adapter or
|
||||
configuration is created only in an isolated branch/worktree/sandbox with
|
||||
no protected-main, production or ambient-secret access.
|
||||
4. A human-authorized governance boundary approves task scope and promotion.
|
||||
Cognitive components may propose and summarize evidence; promotion remains
|
||||
an explicit governance decision.
|
||||
5. Evaluation is layered: contract/architecture/security checks, deterministic
|
||||
unit/integration tests, adversarial and regression corpus, migration/
|
||||
recovery checks, and independent verifier review. Failed, conflicting or
|
||||
incomplete evidence blocks promotion and remains attributable.
|
||||
6. Candidate lifecycle is explicit: `PROPOSED`, `REJECTED`, `SANDBOXED`,
|
||||
`EVALUATING`, `CANARY`, `PROMOTED`, `ROLLED_BACK`, `RETIRED` and `BLOCKED`.
|
||||
Promotion is bounded by capability registration, traffic, budget and
|
||||
telemetry policy. A candidate never receives authority merely by passing
|
||||
model evaluation.
|
||||
7. Canary state keeps the previous implementation addressable. Rollback is a
|
||||
durable, reversible selection change with an emergency stop that disables
|
||||
candidate traffic without deleting evidence, artifacts or lineage.
|
||||
8. Generated skills, modules, adapters and configuration follow the same
|
||||
ordinary development path: typed owners, task scope, architecture checks,
|
||||
tests, security review, migration/recovery plan and independent
|
||||
verification.
|
||||
|
||||
## Ownership and boundaries
|
||||
|
||||
Epistemics owns evidence and ReflectionCandidates; Capability owns
|
||||
implementation/skill registration and lifecycle; Work owns task/delegation
|
||||
execution; Artifact owns candidate and evaluation metadata; Authority owns
|
||||
human promotion permission; Commit/Executor owns external mutation. No new
|
||||
universal LearningManager or self-development owner is introduced.
|
||||
|
||||
Three boundaries are essential: candidate work uses the declared task scope,
|
||||
secret access follows existing CredentialRef policy, and candidate promotion
|
||||
does not itself grant new authority. Original intent, truth assessments and
|
||||
historical evidence retain their existing owners and provenance.
|
||||
|
||||
## Rationale
|
||||
|
||||
Evidence, scoped implementation, independent evaluation and reversible
|
||||
promotion provide a practical improvement loop while keeping authority and
|
||||
provenance explicit. The design uses existing Epistemics, Capability, Work,
|
||||
Artifact and Authority boundaries instead of adding a new universal manager.
|
||||
|
||||
## Consequences, migration and rollback
|
||||
|
||||
The proposal creates an auditable path for improvement while adding review,
|
||||
evaluation and artifact-retention cost. There is no implementation, schema or
|
||||
production change here. Implementation starts with a deterministic proposal
|
||||
fixture and fake candidate, then uses opt-in canary traffic. Rollback disables
|
||||
candidate selection and restores the previous implementation reference;
|
||||
evidence and failed candidates remain available for analysis.
|
||||
|
||||
Revisit if a candidate requires new authority, cross-owner direct storage,
|
||||
unreviewed external mutation, or if rollback cannot restore the previous
|
||||
implementation deterministically.
|
||||
@@ -0,0 +1,35 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/ports"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCanaryDisabled = errors.New("agent canary is disabled")
|
||||
ErrCanaryNotAllowed = errors.New("agent implementation is not canary-allowed")
|
||||
ErrCanaryLimitReached = errors.New("agent canary dispatch limit reached")
|
||||
)
|
||||
|
||||
type CanaryConfig struct {
|
||||
Enabled bool
|
||||
ImplementationID string
|
||||
MaxDispatches int
|
||||
}
|
||||
|
||||
func ExecuteCanaryDryRun(ctx context.Context, store ports.ExecutionStore, config CanaryConfig, usedDispatches int, intent work.AgentDispatchIntent, workerID string, token work.AgentFencingToken, now time.Time, ttl time.Duration) (work.DryRunDispatch, error) {
|
||||
if !config.Enabled {
|
||||
return work.DryRunDispatch{}, ErrCanaryDisabled
|
||||
}
|
||||
if config.ImplementationID == "" || intent.ImplementationID != config.ImplementationID {
|
||||
return work.DryRunDispatch{}, ErrCanaryNotAllowed
|
||||
}
|
||||
if config.MaxDispatches <= 0 || usedDispatches >= config.MaxDispatches {
|
||||
return work.DryRunDispatch{}, ErrCanaryLimitReached
|
||||
}
|
||||
return ExecuteDryRunDispatch(ctx, store, intent, workerID, token, now, ttl)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/domain"
|
||||
"git.cin.su/m/Assistant/modules/work/ports"
|
||||
)
|
||||
|
||||
func ExecuteDryRunDispatch(ctx context.Context, store ports.ExecutionStore, intent work.AgentDispatchIntent, workerID string, token work.AgentFencingToken, now time.Time, ttl time.Duration) (work.DryRunDispatch, error) {
|
||||
dispatch, err := domain.BuildDryRunDispatch(intent, workerID, token, now, ttl)
|
||||
if err != nil {
|
||||
return work.DryRunDispatch{}, err
|
||||
}
|
||||
if err := store.SaveDispatchIntent(ctx, dispatch.Intent); err != nil {
|
||||
return work.DryRunDispatch{}, err
|
||||
}
|
||||
if err := store.SaveAgentLease(ctx, dispatch.Lease); err != nil {
|
||||
return work.DryRunDispatch{}, err
|
||||
}
|
||||
for _, callback := range dispatch.Callbacks {
|
||||
if err := store.AcceptAgentCallback(ctx, callback); err != nil {
|
||||
return work.DryRunDispatch{}, err
|
||||
}
|
||||
}
|
||||
return dispatch, nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
)
|
||||
|
||||
var ErrDryRunDispatchRejected = errors.New("dry-run dispatch rejected")
|
||||
|
||||
func BuildDryRunDispatch(intent work.AgentDispatchIntent, workerID string, token work.AgentFencingToken, now time.Time, ttl time.Duration) (work.DryRunDispatch, error) {
|
||||
if ValidateDispatchIntent(intent) != nil || workerID == "" || token == 0 || now.IsZero() || ttl <= 0 {
|
||||
return work.DryRunDispatch{}, ErrDryRunDispatchRejected
|
||||
}
|
||||
lease, err := AcquireAgentLease(intent.RunID, workerID, token, now, ttl)
|
||||
if err != nil {
|
||||
return work.DryRunDispatch{}, ErrDryRunDispatchRejected
|
||||
}
|
||||
heartbeat := work.AgentCallback{RunID: intent.RunID, WorkerID: workerID, FencingToken: token, Sequence: 1, Kind: work.AgentCallbackHeartbeat, ObservedAt: now.Add(ttl / 3)}
|
||||
result := work.AgentCallback{RunID: intent.RunID, WorkerID: workerID, FencingToken: token, Sequence: 2, Kind: work.AgentCallbackResult, ObservedAt: now.Add((ttl * 2) / 3)}
|
||||
if ValidateAgentCallback(heartbeat, lease, 0) != nil || ValidateAgentCallback(result, lease, heartbeat.Sequence) != nil {
|
||||
return work.DryRunDispatch{}, ErrDryRunDispatchRejected
|
||||
}
|
||||
return work.DryRunDispatch{Intent: intent, Lease: lease, Callbacks: []work.AgentCallback{heartbeat, result}}, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
)
|
||||
|
||||
var ErrLeaseRejected = errors.New("agent lease rejected")
|
||||
|
||||
var ErrCallbackRejected = errors.New("agent callback rejected")
|
||||
|
||||
func AcquireAgentLease(runID work.AgentRunID, workerID string, token work.AgentFencingToken, now time.Time, ttl time.Duration) (work.AgentLease, error) {
|
||||
if runID == "" || workerID == "" || token == 0 || now.IsZero() || ttl <= 0 {
|
||||
return work.AgentLease{}, ErrLeaseRejected
|
||||
}
|
||||
return work.AgentLease{ID: work.AgentLeaseID(string(runID) + ":" + workerID), RunID: runID, WorkerID: workerID, FencingToken: token, State: work.AgentLeaseActive, AcquiredAt: now, ExpiresAt: now.Add(ttl)}, nil
|
||||
}
|
||||
|
||||
func RenewAgentLease(lease work.AgentLease, workerID string, token work.AgentFencingToken, now time.Time, ttl time.Duration) (work.AgentLease, error) {
|
||||
if lease.State != work.AgentLeaseActive || lease.WorkerID != workerID || lease.FencingToken != token || now.IsZero() || !now.Before(lease.ExpiresAt) || ttl <= 0 {
|
||||
return work.AgentLease{}, ErrLeaseRejected
|
||||
}
|
||||
lease.ExpiresAt = now.Add(ttl)
|
||||
return lease, nil
|
||||
}
|
||||
|
||||
func ValidateAgentCallback(callback work.AgentCallback, lease work.AgentLease, lastSequence uint64) error {
|
||||
if lease.State != work.AgentLeaseActive || callback.RunID == "" || callback.RunID != lease.RunID || callback.WorkerID == "" || callback.WorkerID != lease.WorkerID || callback.FencingToken == 0 || callback.FencingToken != lease.FencingToken || callback.Sequence <= lastSequence || callback.Kind == "" || callback.ObservedAt.IsZero() || !callback.ObservedAt.Before(lease.ExpiresAt) {
|
||||
return ErrCallbackRejected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateIndependentVerification(value work.IndependentVerification) error {
|
||||
if value.ProducerRunID == "" || value.VerifierRunID == "" || value.ProducerRunID == value.VerifierRunID || value.VerifierIdentity == "" || value.ArtifactID == "" || value.TestEvidenceRef == "" || value.Summary == "" {
|
||||
return ErrCallbackRejected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDispatchIntent(value work.AgentDispatchIntent) error {
|
||||
if value.DispatchID == "" || value.IdempotencyKey == "" || value.DelegationID == "" || value.RunID == "" || value.ImplementationID == "" || value.ImplementationRevision < 1 || value.SourceRevision == "" || value.WorktreeID == "" || value.VerificationPlanRef == "" || value.CreatedAtUnixMillis < 1 {
|
||||
return ErrCallbackRejected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
)
|
||||
|
||||
type RunLivenessState string
|
||||
|
||||
const (
|
||||
RunLivenessLive RunLivenessState = "LIVE"
|
||||
RunLivenessStale RunLivenessState = "STALE"
|
||||
RunLivenessInvalid RunLivenessState = "INVALID"
|
||||
RunLivenessNotRun RunLivenessState = "NOT_RUNNING"
|
||||
)
|
||||
|
||||
type RunLiveness struct {
|
||||
State RunLivenessState
|
||||
Age time.Duration
|
||||
ObservedAt time.Time
|
||||
}
|
||||
|
||||
func AssessRunLiveness(run work.AgentRun, observedAt time.Time, heartbeatTimeout time.Duration) RunLiveness {
|
||||
result := RunLiveness{ObservedAt: observedAt}
|
||||
if observedAt.IsZero() || heartbeatTimeout <= 0 || run.UpdatedAtUnixMillis < 1 {
|
||||
result.State = RunLivenessInvalid
|
||||
return result
|
||||
}
|
||||
if run.State != work.AgentRunStarted && run.State != work.AgentRunRunning {
|
||||
result.State = RunLivenessNotRun
|
||||
return result
|
||||
}
|
||||
updated := time.UnixMilli(run.UpdatedAtUnixMillis)
|
||||
result.Age = observedAt.Sub(updated)
|
||||
if result.Age < 0 {
|
||||
result.State = RunLivenessInvalid
|
||||
return result
|
||||
}
|
||||
result.State = RunLivenessLive
|
||||
if result.Age > heartbeatTimeout {
|
||||
result.State = RunLivenessStale
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
)
|
||||
|
||||
type ExecutionStore interface {
|
||||
SaveDispatchIntent(context.Context, work.AgentDispatchIntent) error
|
||||
SaveAgentLease(context.Context, work.AgentLease) error
|
||||
AcceptAgentCallback(context.Context, work.AgentCallback) error
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -20,3 +24,84 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
"architecture_generation": h.ArchitectureGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
type Probe struct {
|
||||
Name string
|
||||
Check func(context.Context) error
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
Reason string `json:"reason"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
type ReadinessHandler struct {
|
||||
Runtime string
|
||||
Probes []Probe
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (h ReadinessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
timeout := h.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = 2 * time.Second
|
||||
}
|
||||
results := make([]Result, 0, len(h.Probes))
|
||||
ready := true
|
||||
for _, probe := range h.Probes {
|
||||
result := Result{Name: probe.Name, State: "READY", Reason: "AVAILABLE"}
|
||||
if probe.Check == nil {
|
||||
result.State, result.Reason, result.Detail = "DEGRADED", "PROBE_NOT_CONFIGURED", "probe is not configured"
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), timeout)
|
||||
err := probe.Check(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
ready = false
|
||||
result.State, result.Reason, result.Detail = "DEGRADED", "DEPENDENCY_UNAVAILABLE", err.Error()
|
||||
}
|
||||
}
|
||||
if result.State != "READY" {
|
||||
ready = false
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
status := "ready"
|
||||
code := http.StatusOK
|
||||
if !ready {
|
||||
status, code = "degraded", http.StatusServiceUnavailable
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"status": status, "runtime": h.Runtime, "checks": results})
|
||||
}
|
||||
|
||||
func HTTPProbe(client *http.Client, endpoint string) Probe {
|
||||
return Probe{Name: endpoint, Check: func(ctx context.Context) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return fmt.Errorf("status=%s", response.Status)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
func TCPProbe(name, host, port string) Probe {
|
||||
return Probe{Name: name, Check: func(ctx context.Context) error {
|
||||
connection, err := (&net.Dialer{}).DialContext(ctx, "tcp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return connection.Close()
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadinessReturns503WithTypedDependencyReason(t *testing.T) {
|
||||
handler := ReadinessHandler{Runtime: "test", Probes: []Probe{{Name: "postgres", Check: func(context.Context) error { return errors.New("connection refused") }}}}
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/readyz", nil))
|
||||
if recorder.Code != http.StatusServiceUnavailable || !strings.Contains(recorder.Body.String(), `"reason":"DEPENDENCY_UNAVAILABLE"`) {
|
||||
t.Fatalf("readiness response = %d %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadinessReturns200WhenAllProbesPass(t *testing.T) {
|
||||
handler := ReadinessHandler{Runtime: "test", Probes: []Probe{{Name: "postgres", Check: func(context.Context) error { return nil }}}}
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/readyz", nil))
|
||||
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `"status":"ready"`) {
|
||||
t.Fatalf("readiness response = %d %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/artifact"
|
||||
)
|
||||
|
||||
func TestAgentArtifactAttestationRequiresProvenance(t *testing.T) {
|
||||
attestation := artifact.AgentArtifactAttestation{ArtifactID: "artifact-1", ProducerRunID: "run-1", SourceRevision: "commit-1", WorktreeID: "worktree-1", ContentDigest: "sha256:artifact", TestEvidenceRef: "tests:1", CreatedAtUnixMillis: 10}
|
||||
if err := attestation.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
attestation.ContentDigest = ""
|
||||
if err := attestation.Validate(); err == nil {
|
||||
t.Fatal("expected missing digest rejection")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/application"
|
||||
)
|
||||
|
||||
type canaryStore struct{ writes int }
|
||||
|
||||
func (s *canaryStore) SaveDispatchIntent(context.Context, work.AgentDispatchIntent) error {
|
||||
s.writes++
|
||||
return nil
|
||||
}
|
||||
func (s *canaryStore) SaveAgentLease(context.Context, work.AgentLease) error { s.writes++; return nil }
|
||||
func (s *canaryStore) AcceptAgentCallback(context.Context, work.AgentCallback) error {
|
||||
s.writes++
|
||||
return nil
|
||||
}
|
||||
|
||||
func canaryIntent() work.AgentDispatchIntent {
|
||||
return work.AgentDispatchIntent{DispatchID: "dispatch-1", IdempotencyKey: "idem-1", DelegationID: "delegation-1", RunID: "run-1", ImplementationID: "fake-worker", ImplementationRevision: 1, SourceRevision: "source-1", WorktreeID: "worktree-1", VerificationPlanRef: "verify-1", CreatedAtUnixMillis: 1_700_000_000_000}
|
||||
}
|
||||
|
||||
func TestCanaryRequiresExplicitEnablementAndAllowlist(t *testing.T) {
|
||||
store := &canaryStore{}
|
||||
config := application.CanaryConfig{Enabled: false, ImplementationID: "fake-worker", MaxDispatches: 1}
|
||||
_, err := application.ExecuteCanaryDryRun(context.Background(), store, config, 0, canaryIntent(), "worker-1", 7, time.UnixMilli(1_700_000_000_000), time.Minute)
|
||||
if err != application.ErrCanaryDisabled || store.writes != 0 {
|
||||
t.Fatalf("disabled canary wrote state or returned wrong error: %v/%d", err, store.writes)
|
||||
}
|
||||
config.Enabled = true
|
||||
config.ImplementationID = "other-worker"
|
||||
_, err = application.ExecuteCanaryDryRun(context.Background(), store, config, 0, canaryIntent(), "worker-1", 7, time.UnixMilli(1_700_000_000_000), time.Minute)
|
||||
if err != application.ErrCanaryNotAllowed || store.writes != 0 {
|
||||
t.Fatalf("unallowed canary wrote state or returned wrong error: %v/%d", err, store.writes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanaryHonorsDispatchLimit(t *testing.T) {
|
||||
store := &canaryStore{}
|
||||
config := application.CanaryConfig{Enabled: true, ImplementationID: "fake-worker", MaxDispatches: 1}
|
||||
_, err := application.ExecuteCanaryDryRun(context.Background(), store, config, 1, canaryIntent(), "worker-1", 7, time.UnixMilli(1_700_000_000_000), time.Minute)
|
||||
if err != application.ErrCanaryLimitReached || store.writes != 0 {
|
||||
t.Fatalf("limited canary wrote state or returned wrong error: %v/%d", err, store.writes)
|
||||
}
|
||||
_, err = application.ExecuteCanaryDryRun(context.Background(), store, config, 0, canaryIntent(), "worker-1", 7, time.UnixMilli(1_700_000_000_000), time.Minute)
|
||||
if err != nil || store.writes != 4 {
|
||||
t.Fatalf("enabled canary did not persist expected cycle: %v/%d", err, store.writes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
workdomain "git.cin.su/m/Assistant/modules/work/domain"
|
||||
)
|
||||
|
||||
func TestDispatchIntentRequiresStableExecutionIdentity(t *testing.T) {
|
||||
intent := work.AgentDispatchIntent{DispatchID: "dispatch-1", IdempotencyKey: "idem-1", DelegationID: "delegation-1", RunID: "run-1", ImplementationID: "agent-1", ImplementationRevision: 2, SourceRevision: "commit-1", WorktreeID: "worktree-1", VerificationPlanRef: "verify-plan-1", CreatedAtUnixMillis: 10}
|
||||
if err := workdomain.ValidateDispatchIntent(intent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
intent.IdempotencyKey = ""
|
||||
if err := workdomain.ValidateDispatchIntent(intent); err == nil {
|
||||
t.Fatal("expected missing idempotency rejection")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
workdomain "git.cin.su/m/Assistant/modules/work/domain"
|
||||
)
|
||||
|
||||
func TestBuildDryRunDispatchProducesDeterministicCallbacks(t *testing.T) {
|
||||
now := time.UnixMilli(1_700_000_000_000)
|
||||
intent := work.AgentDispatchIntent{
|
||||
DispatchID: "dispatch-1", IdempotencyKey: "idem-1", DelegationID: "delegation-1", RunID: "run-1",
|
||||
ImplementationID: "fake-worker", ImplementationRevision: 1, SourceRevision: "source-1",
|
||||
WorktreeID: "worktree-1", VerificationPlanRef: "verify-1", CreatedAtUnixMillis: now.UnixMilli(),
|
||||
}
|
||||
first, err := workdomain.BuildDryRunDispatch(intent, "worker-1", 7, now, 15*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := workdomain.BuildDryRunDispatch(intent, "worker-1", 7, now, 15*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if first.CallbackCount() != 2 || first.Lease != second.Lease || first.Callbacks[0] != second.Callbacks[0] || first.Callbacks[1] != second.Callbacks[1] {
|
||||
t.Fatal("dry-run dispatch is not deterministic")
|
||||
}
|
||||
if first.Callbacks[1].Sequence != 2 || !first.LastObservedAt().Before(first.Lease.ExpiresAt) {
|
||||
t.Fatal("callback sequence or lease window is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDryRunDispatchRejectsInvalidIntent(t *testing.T) {
|
||||
_, err := workdomain.BuildDryRunDispatch(work.AgentDispatchIntent{}, "worker-1", 1, time.UnixMilli(1_700_000_000_000), time.Minute)
|
||||
if err != workdomain.ErrDryRunDispatchRejected {
|
||||
t.Fatalf("expected rejection, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/application"
|
||||
)
|
||||
|
||||
type recordingExecutionStore struct {
|
||||
operations []string
|
||||
}
|
||||
|
||||
func (s *recordingExecutionStore) SaveDispatchIntent(context.Context, work.AgentDispatchIntent) error {
|
||||
s.operations = append(s.operations, "intent")
|
||||
return nil
|
||||
}
|
||||
func (s *recordingExecutionStore) SaveAgentLease(context.Context, work.AgentLease) error {
|
||||
s.operations = append(s.operations, "lease")
|
||||
return nil
|
||||
}
|
||||
func (s *recordingExecutionStore) AcceptAgentCallback(context.Context, work.AgentCallback) error {
|
||||
s.operations = append(s.operations, "callback")
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestExecuteDryRunDispatchPersistsInOrder(t *testing.T) {
|
||||
now := time.UnixMilli(1_700_000_000_000)
|
||||
intent := work.AgentDispatchIntent{DispatchID: "dispatch-1", IdempotencyKey: "idem-1", DelegationID: "delegation-1", RunID: "run-1", ImplementationID: "fake-worker", ImplementationRevision: 1, SourceRevision: "source-1", WorktreeID: "worktree-1", VerificationPlanRef: "verify-1", CreatedAtUnixMillis: now.UnixMilli()}
|
||||
store := &recordingExecutionStore{}
|
||||
result, err := application.ExecuteDryRunDispatch(context.Background(), store, intent, "worker-1", 7, now, 15*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.CallbackCount() != 2 || len(store.operations) != 4 || store.operations[0] != "intent" || store.operations[1] != "lease" || store.operations[2] != "callback" || store.operations[3] != "callback" {
|
||||
t.Fatalf("unexpected persistence sequence: %#v", store.operations)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
workdomain "git.cin.su/m/Assistant/modules/work/domain"
|
||||
)
|
||||
|
||||
func TestAgentLeaseFencesWorkerAndToken(t *testing.T) {
|
||||
now := time.Unix(100, 0)
|
||||
lease, err := workdomain.AcquireAgentLease("run-1", "worker-a", 7, now, time.Minute)
|
||||
if err != nil || lease.State != work.AgentLeaseActive {
|
||||
t.Fatalf("lease = %#v %v", lease, err)
|
||||
}
|
||||
if _, err := workdomain.RenewAgentLease(lease, "worker-b", 7, now.Add(time.Second), time.Minute); err == nil {
|
||||
t.Fatal("expected worker fencing")
|
||||
}
|
||||
if _, err := workdomain.RenewAgentLease(lease, "worker-a", 8, now.Add(time.Second), time.Minute); err == nil {
|
||||
t.Fatal("expected token fencing")
|
||||
}
|
||||
if renewed, err := workdomain.RenewAgentLease(lease, "worker-a", 7, now.Add(time.Second), time.Minute); err != nil || !renewed.ExpiresAt.After(lease.ExpiresAt) {
|
||||
t.Fatalf("renewed = %#v %v", renewed, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpiredLeaseCannotRenew(t *testing.T) {
|
||||
now := time.Unix(100, 0)
|
||||
lease, _ := workdomain.AcquireAgentLease("run-1", "worker-a", 1, now, time.Second)
|
||||
if _, err := workdomain.RenewAgentLease(lease, "worker-a", 1, now.Add(time.Second), time.Second); err == nil {
|
||||
t.Fatal("expected expired lease rejection")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentCallbackRejectsReplayAndWrongFencing(t *testing.T) {
|
||||
now := time.Unix(100, 0)
|
||||
lease, _ := workdomain.AcquireAgentLease("run-1", "worker-a", 1, now, time.Minute)
|
||||
callback := work.AgentCallback{RunID: "run-1", WorkerID: "worker-a", FencingToken: 1, Sequence: 2, Kind: work.AgentCallbackHeartbeat, ObservedAt: now.Add(time.Second)}
|
||||
if err := workdomain.ValidateAgentCallback(callback, lease, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := workdomain.ValidateAgentCallback(callback, lease, 2); err == nil {
|
||||
t.Fatal("expected replay rejection")
|
||||
}
|
||||
callback.FencingToken = 2
|
||||
if err := workdomain.ValidateAgentCallback(callback, lease, 1); err == nil {
|
||||
t.Fatal("expected fencing rejection")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDurableAgentExecutionMigrationDefinesOwnedState(t *testing.T) {
|
||||
data, err := os.ReadFile("../../database/migrations/029_work_agent_execution_persistence.sql")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sql := string(data)
|
||||
for _, table := range []string{
|
||||
"work.agent_dispatch_intents",
|
||||
"work.agent_leases",
|
||||
"work.agent_callback_heads",
|
||||
} {
|
||||
if !strings.Contains(sql, "CREATE TABLE IF NOT EXISTS "+table) {
|
||||
t.Fatalf("migration does not define %s", table)
|
||||
}
|
||||
}
|
||||
for _, marker := range []string{
|
||||
"UNIQUE",
|
||||
"CHECK (state IN ('ACTIVE','EXPIRED'))",
|
||||
"CHECK (fencing_token > 0)",
|
||||
} {
|
||||
if !strings.Contains(sql, marker) {
|
||||
t.Fatalf("migration/repository contract marker missing: %s", marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package agent_execution_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
workdomain "git.cin.su/m/Assistant/modules/work/domain"
|
||||
)
|
||||
|
||||
func TestIndependentVerificationRequiresDistinctRun(t *testing.T) {
|
||||
verification := work.IndependentVerification{ProducerRunID: "producer-1", VerifierRunID: "verifier-1", VerifierIdentity: "verifier", ArtifactID: "artifact-1", TestEvidenceRef: "tests-1", Verified: true, Summary: "passed"}
|
||||
if err := workdomain.ValidateIndependentVerification(verification); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verification.VerifierRunID = verification.ProducerRunID
|
||||
if err := workdomain.ValidateIndependentVerification(verification); err == nil {
|
||||
t.Fatal("expected same-run rejection")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cognitive_admission_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func TestAdmitReturnsExplainableDecision(t *testing.T) {
|
||||
policy := capability.AdmissionPolicy{ID: "local-v1", MaxConcurrent: 2, MaxAcceptedPerHour: 10, MaxInputBytes: 1000, MaxOutputBytes: 500}
|
||||
request := capability.AdmissionRequest{Complexity: capability.AdmissionNormal, InputBytes: 100, MaxOutputBytes: 100, DeadlineMillis: 2000}
|
||||
if decision := capability.Admit(request, policy, capability.AdmissionCounters{}, 1000); decision.State != capability.AdmissionAdmitted || decision.PolicyID != policy.ID {
|
||||
t.Fatalf("admitted = %#v", decision)
|
||||
}
|
||||
checks := []struct {
|
||||
counters capability.AdmissionCounters
|
||||
reason capability.AdmissionReason
|
||||
}{
|
||||
{capability.AdmissionCounters{Concurrent: 2}, capability.AdmissionReasonConcurrency},
|
||||
{capability.AdmissionCounters{AcceptedLastHour: 10}, capability.AdmissionReasonAcceptedHour},
|
||||
}
|
||||
for _, check := range checks {
|
||||
if decision := capability.Admit(request, policy, check.counters, 1000); decision.State != capability.AdmissionRejected || decision.Reason != check.reason {
|
||||
t.Fatalf("rejected = %#v want %s", decision, check.reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitRejectsBudgetAndDeadline(t *testing.T) {
|
||||
policy := capability.AdmissionPolicy{ID: "local-v1", MaxConcurrent: 1, MaxAcceptedPerHour: 1, MaxInputBytes: 10, MaxOutputBytes: 10}
|
||||
if decision := capability.Admit(capability.AdmissionRequest{InputBytes: 11, MaxOutputBytes: 1}, policy, capability.AdmissionCounters{}, 1); decision.Reason != capability.AdmissionReasonInputBudget {
|
||||
t.Fatalf("input decision = %#v", decision)
|
||||
}
|
||||
if decision := capability.Admit(capability.AdmissionRequest{InputBytes: 1, MaxOutputBytes: 1, DeadlineMillis: 1}, policy, capability.AdmissionCounters{}, 1); decision.Reason != capability.AdmissionReasonDeadline {
|
||||
t.Fatalf("deadline decision = %#v", decision)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package context_compiler_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
)
|
||||
|
||||
func TestCompileContextOrdersDeduplicatesAndBudgets(t *testing.T) {
|
||||
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{
|
||||
MaxBytes: 4,
|
||||
Candidates: []cognitive.ContextCandidate{
|
||||
{Excerpt: cognitive.ContextExcerpt{ID: "low", Sensitivity: cognitive.SensitivityPublic, Text: "low"}, Relevance: 1},
|
||||
{Excerpt: cognitive.ContextExcerpt{ID: "high", Sensitivity: cognitive.SensitivityPublic, Text: "éé"}, Relevance: 2},
|
||||
{Excerpt: cognitive.ContextExcerpt{ID: "high", Sensitivity: cognitive.SensitivityPublic, Text: "hi"}, Relevance: 3},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(bundle.Excerpts) != 1 || bundle.Excerpts[0].ID != "high" || bundle.Bytes != 2 {
|
||||
t.Fatalf("unexpected bundle: %#v", bundle)
|
||||
}
|
||||
if len(bundle.Omissions) != 2 || bundle.Omissions[0].Reason != "duplicate" || bundle.Omissions[1].Reason != "budget" {
|
||||
t.Fatalf("unexpected omissions: %#v", bundle.Omissions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileContextFiltersSensitivityByPolicy(t *testing.T) {
|
||||
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{
|
||||
MaxBytes: 100,
|
||||
DataPolicy: capability.DataPolicyExternalMinimizedContext,
|
||||
Candidates: []cognitive.ContextCandidate{
|
||||
{Excerpt: cognitive.ContextExcerpt{ID: "public", Sensitivity: cognitive.SensitivityPublic, Text: "ok"}},
|
||||
{Excerpt: cognitive.ContextExcerpt{ID: "private", Sensitivity: cognitive.SensitivityPrivateAllowed, Text: "private"}},
|
||||
{Excerpt: cognitive.ContextExcerpt{ID: "secret", Sensitivity: cognitive.SensitivitySecret, Text: "secret"}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(bundle.Excerpts) != 1 || bundle.Excerpts[0].ID != "public" {
|
||||
t.Fatalf("unexpected excerpts: %#v", bundle.Excerpts)
|
||||
}
|
||||
if len(bundle.Omissions) != 2 {
|
||||
t.Fatalf("unexpected omissions: %#v", bundle.Omissions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileContextAllowsEmptyResultAndRejectsInvalidBudget(t *testing.T) {
|
||||
if _, err := cognitive.CompileContext(cognitive.ContextCompileInput{}); err == nil {
|
||||
t.Fatal("expected invalid budget")
|
||||
}
|
||||
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{MaxBytes: 10, Candidates: []cognitive.ContextCandidate{{Excerpt: cognitive.ContextExcerpt{ID: "secret", Sensitivity: cognitive.SensitivitySecret, Text: "x"}}}})
|
||||
if err != nil || len(bundle.Excerpts) != 0 || len(bundle.Omissions) != 1 {
|
||||
t.Fatalf("unexpected empty result: %#v %v", bundle, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package model_qualification_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func TestQualificationManifestRequiresAllEvidenceDimensions(t *testing.T) {
|
||||
dimensions := []capability.QualificationDimension{capability.DimensionDialogue, capability.DimensionInterpretation, capability.DimensionCoding, capability.DimensionResearch, capability.DimensionUncertainty, capability.DimensionContext, capability.DimensionLatency, capability.DimensionResources, capability.DimensionRestart}
|
||||
measurements := make([]capability.QualificationMeasurement, 0, len(dimensions))
|
||||
for _, dimension := range dimensions {
|
||||
measurements = append(measurements, capability.QualificationMeasurement{Dimension: dimension, Result: capability.EvaluationSupported, Artifact: "artifact:" + string(dimension)})
|
||||
}
|
||||
manifest := capability.QualificationManifest{ID: "manifest-1", ModelDescriptorID: "model-1", ModelRevision: 2, ModelFingerprint: "sha256:model", BenchmarkVersion: "holdout-v1", HoldoutCorpusDigest: "sha256:corpus", LicenseEvidence: "license:approved", DataPolicyEvidence: "policy:local", Measurements: measurements, ObservedAtUnixMillis: 10}
|
||||
if err := manifest.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
manifest.Measurements = manifest.Measurements[:len(manifest.Measurements)-1]
|
||||
if err := manifest.Validate(); err == nil {
|
||||
t.Fatal("expected incomplete manifest to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQualificationGateRejectsUnsupportedEvidence(t *testing.T) {
|
||||
manifest := completeManifest()
|
||||
if state := manifest.QualificationState(); state != capability.QualificationValidated {
|
||||
t.Fatalf("state = %s", state)
|
||||
}
|
||||
manifest.Measurements[0].Result = capability.EvaluationUnsupported
|
||||
if state := manifest.QualificationState(); state != capability.QualificationRejected {
|
||||
t.Fatalf("unsupported state = %s", state)
|
||||
}
|
||||
}
|
||||
|
||||
func completeManifest() capability.QualificationManifest {
|
||||
dimensions := []capability.QualificationDimension{capability.DimensionDialogue, capability.DimensionInterpretation, capability.DimensionCoding, capability.DimensionResearch, capability.DimensionUncertainty, capability.DimensionContext, capability.DimensionLatency, capability.DimensionResources, capability.DimensionRestart}
|
||||
measurements := make([]capability.QualificationMeasurement, 0, len(dimensions))
|
||||
for _, dimension := range dimensions {
|
||||
measurements = append(measurements, capability.QualificationMeasurement{Dimension: dimension, Result: capability.EvaluationSupported, Artifact: "artifact:" + string(dimension)})
|
||||
}
|
||||
return capability.QualificationManifest{ID: "manifest-1", ModelDescriptorID: "model-1", ModelRevision: 2, ModelFingerprint: "sha256:model", BenchmarkVersion: "holdout-v1", HoldoutCorpusDigest: "sha256:corpus", LicenseEvidence: "license:approved", DataPolicyEvidence: "policy:local", Measurements: measurements, ObservedAtUnixMillis: 10}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$script = Get-Content -LiteralPath (Join-Path $PSScriptRoot '../../deploy/verify-latest-postgres-backup.sh') -Raw
|
||||
if ($script -notmatch "-regex '\.\*/\[0-9\]\{8\}T\[0-9\]\{6\}Z\$'") {
|
||||
throw 'restore selector does not restrict candidates to timestamp directories'
|
||||
}
|
||||
if ($script -notmatch 'invalid timestamp') { throw 'restore selector lacks timestamp fail-closed guard' }
|
||||
if ($script -match "! -name '\.\*'") { throw 'restore selector still accepts arbitrary non-hidden directories' }
|
||||
Write-Output 'verify-latest backup selector tests: PASS'
|
||||
@@ -0,0 +1,52 @@
|
||||
package research_contracts_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
)
|
||||
|
||||
func TestResearchContractsRequireExactEvidence(t *testing.T) {
|
||||
plan := epistemics.ResearchPlan{ID: "plan-1", Question: "What changed?", SourceClasses: []string{"repository"}, FreshnessPolicy: "same-day"}
|
||||
if err := plan.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshot := epistemics.SourceSnapshot{ID: "snapshot-1", SourceID: "source-1", ContentDigest: "sha256:abc", CapturedAtUnixMillis: 1, Freshness: epistemics.FreshnessCurrent}
|
||||
if err := snapshot.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
excerpt := epistemics.SourceExcerpt{ID: "excerpt-1", SnapshotID: snapshot.ID, Text: "exact evidence", Start: 0, End: 14}
|
||||
if err := excerpt.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
citation := epistemics.Citation{ID: "citation-1", ExcerptID: excerpt.ID, Evidence: epistemics.EvidenceObserved, Coverage: epistemics.CoverageComplete, Conflict: epistemics.ConflictNone, ResolvedAtUnixMillis: 2}
|
||||
if err := citation.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResearchContractsPreserveUnknownAndAssumedStates(t *testing.T) {
|
||||
snapshot := epistemics.SourceSnapshot{ID: "snapshot-unknown", SourceID: "source-1", ContentDigest: "sha256:abc", CapturedAtUnixMillis: 1, Freshness: epistemics.FreshnessUnknown}
|
||||
if err := snapshot.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
citation := epistemics.Citation{ID: "citation-assumed", ExcerptID: "excerpt-1", Evidence: epistemics.EvidenceAssumed, Coverage: epistemics.CoveragePartial, Conflict: epistemics.ConflictPresent, ResolvedAtUnixMillis: 2}
|
||||
if err := citation.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCitationRequiresMatchingSourceChain(t *testing.T) {
|
||||
source := epistemics.Source{ID: "source-1", Locator: "https://example.test/report"}
|
||||
snapshot := epistemics.SourceSnapshot{ID: "snapshot-1", SourceID: source.ID, ContentDigest: "sha256:abc", CapturedAtUnixMillis: 1, Freshness: epistemics.FreshnessStale}
|
||||
excerpt := epistemics.SourceExcerpt{ID: "excerpt-1", SnapshotID: snapshot.ID, Text: "exact evidence", Start: 0, End: 14}
|
||||
citation, err := epistemics.ResolveCitation(source, snapshot, excerpt, epistemics.EvidenceObserved, epistemics.CoveragePartial, epistemics.ConflictPresent, 2)
|
||||
if err != nil || citation.ExcerptID != excerpt.ID || citation.Conflict != epistemics.ConflictPresent {
|
||||
t.Fatalf("unexpected citation: %#v %v", citation, err)
|
||||
}
|
||||
wrong := snapshot
|
||||
wrong.SourceID = "other-source"
|
||||
if _, err := epistemics.ResolveCitation(source, wrong, excerpt, epistemics.EvidenceObserved, epistemics.CoverageComplete, epistemics.ConflictNone, 2); err == nil {
|
||||
t.Fatal("expected source-chain mismatch")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package work_recovery_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
workdomain "git.cin.su/m/Assistant/modules/work/domain"
|
||||
)
|
||||
|
||||
func TestAssessRunLivenessClassifiesDurableHeartbeat(t *testing.T) {
|
||||
now := time.UnixMilli(10000)
|
||||
run := work.AgentRun{ID: "run-1", DelegationID: "delegation-1", ImplementationID: "agent-1", ImplementationRevision: 1, Protocol: work.AgentExecutionProtocol("v1"), State: work.AgentRunRunning, StartedAtUnixMillis: 9000, UpdatedAtUnixMillis: 9000}
|
||||
if got := workdomain.AssessRunLiveness(run, now.Add(100*time.Millisecond), time.Second); got.State != workdomain.RunLivenessStale || got.Age != 1100*time.Millisecond {
|
||||
t.Fatalf("stale = %#v", got)
|
||||
}
|
||||
run.UpdatedAtUnixMillis = 9500
|
||||
if got := workdomain.AssessRunLiveness(run, now, time.Second); got.State != workdomain.RunLivenessLive {
|
||||
t.Fatalf("live = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssessRunLivenessDoesNotTreatCompletedAsStale(t *testing.T) {
|
||||
run := work.AgentRun{State: work.AgentRunCompleted, UpdatedAtUnixMillis: 1}
|
||||
got := workdomain.AssessRunLiveness(run, time.UnixMilli(10000), time.Second)
|
||||
if got.State != workdomain.RunLivenessNotRun {
|
||||
t.Fatalf("completed = %#v", got)
|
||||
}
|
||||
invalid := workdomain.AssessRunLiveness(run, time.Time{}, time.Second)
|
||||
if invalid.State != workdomain.RunLivenessInvalid {
|
||||
t.Fatalf("invalid = %#v", invalid)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user