Compare commits

...
Author SHA1 Message Date
m 22b84ead9c fix: grant development gate owner access
CI / test (push) Successful in 1m9s
CI / test (pull_request) Successful in 1m8s
2026-08-23 03:45:36 +03:00
m 42c7df964b Merge pull request 'feat: persist governed development gates' (#173) from codex/wp-701-durable-development-gate into main
CI / test (push) Successful in 1m4s
2026-08-23 00:37:34 +00:00
m c542642bf0 feat: persist governed development gates
CI / test (push) Successful in 1m8s
CI / test (pull_request) Successful in 1m4s
2026-08-23 03:34:43 +03:00
m fde14cf0b8 Merge pull request 'docs: record durable agent canary evidence' (#172) from codex/wp-301-agent-canary-live-evidence into main
CI / test (push) Successful in 1m6s
2026-08-23 00:22:21 +00:00
m b52590c458 docs: record durable agent canary evidence
CI / test (push) Successful in 1m6s
CI / test (pull_request) Successful in 1m6s
2026-08-23 03:18:57 +03:00
m 6854458e21 Merge pull request 'fix: make agent canary replayable' (#171) from codex/wp-301-agent-canary-replayable into main
CI / test (push) Successful in 1m6s
2026-08-23 00:11:50 +00:00
m 5658bed520 fix: make agent canary replayable
CI / test (push) Successful in 1m4s
CI / test (pull_request) Successful in 1m4s
2026-08-23 03:10:07 +03:00
m 7252296a74 Merge pull request 'fix: protect agent canary database credentials' (#170) from codex/wp-301-agent-canary-protected-config into main
CI / test (push) Successful in 1m2s
2026-08-23 00:05:20 +00:00
12 changed files with 198 additions and 9 deletions
+54 -3
View File
@@ -14,9 +14,10 @@ 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")
ErrDispatchIntentConflict = errors.New("dispatch intent idempotency conflict")
ErrAgentLeaseUnavailable = errors.New("agent lease unavailable")
ErrAgentCallbackRejected = errors.New("agent callback rejected")
ErrDevelopmentGateConflict = errors.New("development gate revision conflict")
)
func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, error) {
@@ -49,6 +50,56 @@ func (r *WorkRepository) Load(ctx context.Context, id work.TaskID) (work.TaskTar
return target, nil
}
func (r *WorkRepository) LoadDevelopmentGate(ctx context.Context, id string) (work.DevelopmentGate, error) {
var gate work.DevelopmentGate
var evidence, producer, verifier []string
err := r.pool.QueryRow(ctx, `SELECT gate_id,revision,updated_at_unix_millis,stage,evidence_refs,proposal_ref,task_packet_ref,worktree_ref,producer_run_refs,verifier_run_refs,verification_evidence,canary_evidence,promotion_ref,rollback_ref FROM work.development_gates WHERE gate_id=$1`, id).Scan(
&gate.ID, &gate.Revision, &gate.UpdatedAtUnixMillis, &gate.Stage, &evidence, &gate.ProposalRef, &gate.TaskPacketRef, &gate.WorktreeRef, &producer, &verifier, &gate.VerificationEvidence, &gate.CanaryEvidence, &gate.PromotionRef, &gate.RollbackRef)
if err != nil {
return work.DevelopmentGate{}, fmt.Errorf("load development gate: %w", err)
}
gate.EvidenceRefs = append([]string(nil), evidence...)
for _, run := range producer {
gate.ProducerRunRefs = append(gate.ProducerRunRefs, work.AgentRunID(run))
}
for _, run := range verifier {
gate.VerifierRunRefs = append(gate.VerifierRunRefs, work.AgentRunID(run))
}
return gate, nil
}
func (r *WorkRepository) SaveDevelopmentGate(ctx context.Context, gate work.DevelopmentGate, expectedRevision int64) error {
if gate.ID == "" || expectedRevision < 0 || gate.Revision < 1 || gate.UpdatedAtUnixMillis < 1 {
return ErrDevelopmentGateConflict
}
producer := make([]string, 0, len(gate.ProducerRunRefs))
for _, run := range gate.ProducerRunRefs {
producer = append(producer, string(run))
}
verifier := make([]string, 0, len(gate.VerifierRunRefs))
for _, run := range gate.VerifierRunRefs {
verifier = append(verifier, string(run))
}
if expectedRevision == 0 {
command, err := r.pool.Exec(ctx, `INSERT INTO work.development_gates(gate_id,revision,updated_at_unix_millis,stage,evidence_refs,proposal_ref,task_packet_ref,worktree_ref,producer_run_refs,verifier_run_refs,verification_evidence,canary_evidence,promotion_ref,rollback_ref) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT(gate_id) DO NOTHING`, gate.ID, gate.Revision, gate.UpdatedAtUnixMillis, gate.Stage, gate.EvidenceRefs, gate.ProposalRef, gate.TaskPacketRef, gate.WorktreeRef, producer, verifier, gate.VerificationEvidence, gate.CanaryEvidence, gate.PromotionRef, gate.RollbackRef)
if err != nil {
return err
}
if command.RowsAffected() != 1 {
return ErrDevelopmentGateConflict
}
return nil
}
command, err := r.pool.Exec(ctx, `UPDATE work.development_gates SET revision=$1,updated_at_unix_millis=$2,stage=$3,evidence_refs=$4,proposal_ref=$5,task_packet_ref=$6,worktree_ref=$7,producer_run_refs=$8,verifier_run_refs=$9,verification_evidence=$10,canary_evidence=$11,promotion_ref=$12,rollback_ref=$13 WHERE gate_id=$14 AND revision=$15`, gate.Revision, gate.UpdatedAtUnixMillis, gate.Stage, gate.EvidenceRefs, gate.ProposalRef, gate.TaskPacketRef, gate.WorktreeRef, producer, verifier, gate.VerificationEvidence, gate.CanaryEvidence, gate.PromotionRef, gate.RollbackRef, gate.ID, expectedRevision)
if err != nil {
return err
}
if command.RowsAffected() != 1 {
return ErrDevelopmentGateConflict
}
return nil
}
func (r *WorkRepository) SaveDispatchIntent(ctx context.Context, intent work.AgentDispatchIntent) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
+7 -2
View File
@@ -34,10 +34,15 @@ 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")
runID := flag.String("run-id", "", "optional unique canary run suffix; generated when omitted")
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()}
suffix := *runID
if suffix == "" {
suffix = fmt.Sprintf("%d", time.Now().UnixNano())
}
now := time.Now().UTC()
intent := work.AgentDispatchIntent{DispatchID: "manual-dispatch-" + suffix, IdempotencyKey: "manual-idempotency-" + suffix, DelegationID: work.DelegationID("manual-delegation-" + suffix), RunID: work.AgentRunID("manual-run-" + suffix), ImplementationID: *implementation, ImplementationRevision: 1, SourceRevision: "manual-source-" + suffix, WorktreeID: "manual-worktree-" + suffix, VerificationPlanRef: "manual-verification-" + suffix, CreatedAtUnixMillis: now.UnixMilli()}
var store ports.ExecutionStore = &memoryStore{}
var closeStore func()
if *postgresMode {
+2
View File
@@ -17,6 +17,8 @@ const (
type DevelopmentGate struct {
ID string
Revision int64
UpdatedAtUnixMillis int64
Stage DevelopmentStage
EvidenceRefs []string
ProposalRef string
@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS work.development_gates (
gate_id TEXT PRIMARY KEY,
revision BIGINT NOT NULL CHECK (revision > 0),
updated_at_unix_millis BIGINT NOT NULL CHECK (updated_at_unix_millis > 0),
stage TEXT NOT NULL,
evidence_refs TEXT[] NOT NULL,
proposal_ref TEXT NOT NULL,
task_packet_ref TEXT NOT NULL,
worktree_ref TEXT NOT NULL,
producer_run_refs TEXT[] NOT NULL,
verifier_run_refs TEXT[] NOT NULL,
verification_evidence TEXT NOT NULL,
canary_evidence TEXT NOT NULL,
promotion_ref TEXT NOT NULL,
rollback_ref TEXT NOT NULL
);
@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE work.development_gates OWNER TO work_role;
GRANT SELECT, INSERT, UPDATE ON work.development_gates TO work_role;
COMMIT;
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000194-agent-canary-protected-storage.json",
"task_id": "DEV-000194",
"task_file": "dev/tasks/DEV-000197-durable-development-gate.json",
"task_id": "DEV-000197",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000195",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "7252296a74ab7ff236fae865d63692e0fc6a777d",
"risk_tier": "E",
"intent": "Make the explicit Agent Execution dry-run canary replayable by generating unique dispatch, lease and callback identities per invocation while retaining durable evidence.",
"requirement_ids": ["Agent Execution runtime and recovery", "replayable durable canary", "lease/callback evidence"],
"allowed_change_scope": {"modules": ["agent-canary"], "paths": ["cmd/agent-canary/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000195-replayable-agent-canary-run-ids.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "modules/**", "contracts/**", ".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 canary dry-run contract", "existing durable Work execution records"], "may_change": ["canary identity generation"]},
"semantic_owner": "work-agent-execution",
"canonical_state": ["work.agent_dispatch_intents", "work.agent_leases", "work.agent_callback_heads"],
"state_transitions": ["each explicit canary invocation creates a fresh dispatch identity and durable lease/callback chain"],
"side_effects_and_privileges": ["manual dry-run only; no external execution or source mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["repeated invocation does not reuse prior canary identity; lease conflicts remain explicit errors"],
"assumptions": ["the protected-storage canary configuration from DEV-000194 is already in main"],
"acceptance_scenarios": ["default invocation generates a unique suffix", "operator-supplied run-id remains attributable", "two production canary invocations persist independent durable 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", "CI", "production canary and public health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000196",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "6854458e21ad7ec1c4b736dfabf916ae4f7fac12",
"risk_tier": "E",
"intent": "Record independently verified production evidence for the protected replayable Agent Execution dry-run canary.",
"requirement_ids": ["Agent Execution runtime and recovery", "durable canary evidence"],
"allowed_change_scope": {"modules": ["governance status", "evidence"], "paths": ["docs/PROJECT_STATUS.md", "docs/evidence/agent-execution-dry-run-canary-2026-08-23-v1.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000196-agent-canary-live-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "modules/**", "contracts/**", ".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 protected canary contract", "existing durable Work execution records"], "may_change": ["status projection and evidence only"]},
"semantic_owner": "governance",
"canonical_state": ["work.agent_dispatch_intents", "work.agent_leases", "work.agent_callback_heads"],
"state_transitions": ["none; evidence records already completed canary state"],
"side_effects_and_privileges": ["read-only status/evidence change after an explicit manual dry-run canary"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["no runtime retry; retain the negative external-bridge finding"],
"assumptions": ["the production canary output and database rows were captured without secret values"],
"acceptance_scenarios": ["evidence contains callback_count 2", "evidence contains durable dispatch intent, ACTIVE lease and callback sequence 2", "status explicitly retains the external bridge gap"],
"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", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000197",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "fde14cf0b81e2772c81ee7cb9b0335b56943f6e4",
"risk_tier": "E",
"intent": "Persist the governed self-development gate as a Work-owned revisioned state machine with optimistic concurrency, without executing code or granting promotion authority.",
"requirement_ids": ["WP-701", "durable governed self-development provenance", "CAS stage transitions"],
"allowed_change_scope": {"modules": ["work", "postgres persistence"], "paths": ["contracts/work/self_development.go", "modules/work/ports/development.go", "adapters/postgres/work_repository.go", "database/migrations/037_work_development_gates.sql", "database/migrations/038_work_development_gates_privileges.sql", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000197-durable-development-gate.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "modules/identity/**", "contracts/authority/**", ".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": ["existing DevelopmentGate state machine", "Work-owned persistence boundary"], "may_change": ["revision metadata on DevelopmentGate"]},
"semantic_owner": "work-agent-execution",
"canonical_state": ["work.development_gates"],
"state_transitions": ["persist initial evidence gate", "advance only through caller-validated stage with expected revision", "reject stale revision"],
"side_effects_and_privileges": ["database persistence only; no worktree, provider, source, deployment or promotion mutation"],
"persistence_and_migrations": ["037_work_development_gates.sql", "038_work_development_gates_privileges.sql"],
"failure_retry_restart": ["CAS conflict is explicit and retry requires reloading the gate; restart reloads durable provenance"],
"assumptions": ["domain AdvanceDevelopmentGate remains the sole stage transition validator", "external bridge is a later independently operated capability"],
"acceptance_scenarios": ["initial gate persists at revision 1", "update with expected revision succeeds", "stale revision is rejected", "all provenance fields round-trip without secrets", "work_role can read and write the table"],
"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 -task dev/tasks/DEV-000197-durable-development-gate.json", "git diff --check", "CI", "production migration and health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "REQUIRES_MIGRATION_DECISION"]
}
+2 -2
View File
@@ -6,7 +6,7 @@
- `FAST_PROVIDER_STATUS = LIVE_SMOKE_SUPPORTED`: local CPU gateway authentication is configured through a protected file reference; four bounded local-only advisory cases completed through cognitive-worker. Evidence: `docs/evidence/wp-601-fast-gateway-live-smoke-2026-08-22-v1.json`.
- `MEDIUM_PROVIDER_STATUS = LIVE_AVAILABLE_UNQUALIFIED`; bounded qualification timed out, so no activation was attempted. Evidence: `docs/evidence/wp-200-medium-qualification-2026-08-22-v3.json`.
- `HEAVY_PROVIDER_STATUS = LIVE_QUALIFIED_UNACTIVATED`; the qualified GPU binding remains inactive. Evidence: `docs/evidence/wp-200-heavy-qualification-2026-08-22-v2.json`.
- `AGENT_EXECUTION_STATUS = RECOVERY_ONLY`; no independently operated bridge endpoint is configured. Durable recovery and status endpoints remain active.
- `AGENT_EXECUTION_STATUS = DURABLE_DRY_RUN_CANARY_VERIFIED`; the explicit production canary persisted a dispatch intent, an ACTIVE lease and callback head sequence 2. No independently operated bridge endpoint is configured, so bridge-backed execution remains unavailable. Evidence: `docs/evidence/agent-execution-dry-run-canary-2026-08-23-v1.json`.
- `FLEET_EXECUTION_EVIDENCE = docs/evidence/audit-runtime-status-2026-08-23-v1.json`; FAST, MEDIUM and LIFEBOAT are ready, HEAVY is explicitly degraded by provider timeout, FAST remains preferred/selected, and LIFEBOAT is the fallback tier.
- `TRUTH_RESEARCH_STATUS = LIVE_CANARY_PERSISTED`; the web research canary preserved an OBSERVED/COMPLETE/NO-CONFLICT source chain while leaving the claim UNVERIFIED and belief PROVISIONAL. Evidence: `docs/evidence/truth-runtime-canary-2026-08-23-v1.json`.
- `LEARNING_REFLECTION_STATUS = LIVE_RECONCILED`; reflection worker returned no pending candidate and the durable state contains 31 RESOLVED candidates with no OPEN backlog. Evidence: `docs/evidence/reflection-runtime-reconciliation-2026-08-23-v1.json`.
@@ -16,7 +16,7 @@
## Current governance checkpoint
- `ACTIVE_TASK = dev/tasks/DEV-000190-live-audit-status-sync.json revision 1`
- `ACTIVE_TASK = dev/tasks/DEV-000196-agent-canary-live-evidence.json revision 1`
- `LINEAGE_DECISION = docs/GOVERNANCE_LINEAGE_DECISION_2026-08-22.md`; historical packets remain provenance and are not current authority.
- `REGISTRY_STATUS = YAML and JSON semantic mirrors agree`
- `PRODUCTION_DOMAIN = docker.cin.su`; shared docker-test is not used for this project.
@@ -0,0 +1,25 @@
{
"captured_at": "2026-08-23",
"environment": "production",
"endpoint": "internal agent-canary profile on docker.cin.su",
"run_id": "manual-run-1787444046393734499",
"dispatch_id": "manual-dispatch-1787444046393734499",
"canary_output": {
"callback_count": 2,
"store_writes": 0
},
"durable_database_verification": {
"dispatch_intent": "manual-dispatch-1787444046393734499",
"lease": {
"run_id": "manual-run-1787444046393734499",
"worker_id": "manual-worker",
"state": "ACTIVE"
},
"callback_head": {
"run_id": "manual-run-1787444046393734499",
"last_sequence": 2
}
},
"interpretation": "The explicit protected dry-run canary is replayable and its dispatch, lease and callback chain are durable. This does not prove execution through an independently operated external bridge; the production agent worker remains recovery-only until that bridge is configured and qualified.",
"secrets_exposed": false
}
+15
View File
@@ -0,0 +1,15 @@
package ports
import (
"context"
"git.cin.su/m/Assistant/contracts/work"
)
// DevelopmentGateStore persists only the governed state machine and its
// provenance. It does not create worktrees, execute code or grant promotion
// authority.
type DevelopmentGateStore interface {
LoadDevelopmentGate(context.Context, string) (work.DevelopmentGate, error)
SaveDevelopmentGate(context.Context, work.DevelopmentGate, int64) error
}