Compare commits

..
Author SHA1 Message Date
m b7a4c0f83e chore: activate live audit status task
CI / test (push) Successful in 1m1s
CI / test (pull_request) Successful in 1m7s
2026-08-23 02:21:24 +03:00
m 50d4bdf174 docs: synchronize live audit status evidence
CI / test (push) Failing after 44s
CI / test (pull_request) Failing after 47s
2026-08-23 02:19:12 +03:00
m 5b92cd6a58 Merge pull request 'fix: serialize graph synthesis request fields' (#163) from codex/wp-403-graph-persistence into main
CI / test (push) Successful in 1m17s
2026-08-22 23:10:08 +00:00
m 18d0911810 fix: serialize graph synthesis request fields
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 1m3s
2026-08-23 02:08:24 +03:00
m dcf8cbc855 Merge pull request 'feat: persist verified graph synthesis runtime' (#162) from codex/wp-403-graph-persistence into main
CI / test (push) Successful in 1m16s
2026-08-22 22:56:25 +00:00
m 3d30681279 feat: persist verified graph synthesis runtime
CI / test (push) Successful in 3m1s
CI / test (pull_request) Successful in 2m1s
2026-08-23 01:52:39 +03:00
m 41bc4b567b Merge pull request 'feat: gate graph synthesis on independent verification' (#161) from codex/wp-403-conflict-synthesis into main
CI / test (push) Successful in 1m3s
2026-08-22 22:45:18 +00:00
14 changed files with 271 additions and 33 deletions
+20
View File
@@ -199,6 +199,26 @@ func (r *WorkRepository) LoadWorkGraph(ctx context.Context, graphID string, revi
return graph, nil
}
func (r *WorkRepository) SaveGraphSynthesis(ctx context.Context, conflict work.ConflictSet, candidates []work.ResultCandidate, synthesis work.GraphSynthesisCandidate) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
for _, candidate := range candidates {
if _, err := tx.Exec(ctx, `INSERT INTO work.graph_result_candidates(result_candidate_id,graph_id,graph_revision,node_id,author_run_id,artifact_refs,evidence_summary,summary) VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(result_candidate_id) DO NOTHING`, candidate.ID, candidate.GraphID, candidate.GraphRevision, candidate.NodeID, candidate.AuthorRunID, candidate.ArtifactRefs, candidate.EvidenceSummary, candidate.Summary); err != nil {
return err
}
}
if _, err := tx.Exec(ctx, `INSERT INTO work.graph_conflicts(conflict_id,graph_id,graph_revision,node_id,candidate_ids,resolved) VALUES($1,$2,$3,$4,$5,$6) ON CONFLICT(conflict_id) DO NOTHING`, conflict.ID, conflict.GraphID, conflict.GraphRevision, conflict.NodeID, conflict.CandidateIDs, conflict.Resolved); err != nil {
return err
}
if _, err := tx.Exec(ctx, `INSERT INTO work.graph_synthesis_candidates(synthesis_id,graph_id,graph_revision,node_id,candidate_ids,selected_candidate_id,dissent_candidate_ids,verifier_run_id,verifier_identity,evidence_refs,summary,state) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT(synthesis_id) DO NOTHING`, synthesis.ID, synthesis.GraphID, synthesis.GraphRevision, synthesis.NodeID, synthesis.CandidateIDs, synthesis.SelectedCandidateID, synthesis.DissentCandidateIDs, synthesis.VerifierRunID, synthesis.VerifierIdentity, synthesis.EvidenceRefs, synthesis.Summary, synthesis.State); err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *WorkRepository) RecoverStaleClaims(ctx context.Context, nowUnixMillis, staleAfterMillis int64) (int64, error) {
command, err := r.pool.Exec(ctx, `UPDATE work.agent_dispatch_queue SET state='QUEUED',claimed_by='',updated_at_unix_millis=$1 WHERE state='CLAIMED' AND updated_at_unix_millis <= $2`, nowUnixMillis, nowUnixMillis-staleAfterMillis)
if err != nil {
+13
View File
@@ -55,6 +55,19 @@ func main() {
}
writeJSON(w, graph)
})
mux.HandleFunc("POST /v1/work-graphs/synthesis", func(w http.ResponseWriter, r *http.Request) {
var request application.GraphSynthesisRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<20)).Decode(&request); err != nil {
http.Error(w, "invalid graph synthesis request", http.StatusBadRequest)
return
}
conflict, synthesis, err := application.SynthesizeGraph(r.Context(), repository, request)
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
writeJSON(w, map[string]any{"conflict": conflict, "synthesis": synthesis})
})
addr := os.Getenv("LISTEN_ADDR")
if addr == "" {
addr = ":8088"
+26 -26
View File
@@ -53,23 +53,23 @@ type WorkGraph struct {
}
type ResultCandidate struct {
ID string
GraphID string
GraphRevision int64
NodeID GraphNodeID
AuthorRunID AgentRunID
ArtifactRefs []EvidenceRef
EvidenceSummary string
Summary string
ID string `json:"id"`
GraphID string `json:"graph_id"`
GraphRevision int64 `json:"graph_revision"`
NodeID GraphNodeID `json:"node_id"`
AuthorRunID AgentRunID `json:"author_run_id"`
ArtifactRefs []EvidenceRef `json:"artifact_refs"`
EvidenceSummary string `json:"evidence_summary"`
Summary string `json:"summary"`
}
type ConflictSet struct {
ID string
GraphID string
GraphRevision int64
NodeID GraphNodeID
CandidateIDs []string
Resolved bool
ID string `json:"id"`
GraphID string `json:"graph_id"`
GraphRevision int64 `json:"graph_revision"`
NodeID GraphNodeID `json:"node_id"`
CandidateIDs []string `json:"candidate_ids"`
Resolved bool `json:"resolved"`
}
type GraphSynthesisState string
@@ -80,16 +80,16 @@ const GraphSynthesisVerified GraphSynthesisState = "VERIFIED"
// independently verified selection. It is a candidate, not an external action
// or authority grant.
type GraphSynthesisCandidate struct {
ID string
GraphID string
GraphRevision int64
NodeID GraphNodeID
CandidateIDs []string
SelectedCandidateID string
DissentCandidateIDs []string
VerifierRunID AgentRunID
VerifierIdentity string
EvidenceRefs []EvidenceRef
Summary string
State GraphSynthesisState
ID string `json:"id"`
GraphID string `json:"graph_id"`
GraphRevision int64 `json:"graph_revision"`
NodeID GraphNodeID `json:"node_id"`
CandidateIDs []string `json:"candidate_ids"`
SelectedCandidateID string `json:"selected_candidate_id"`
DissentCandidateIDs []string `json:"dissent_candidate_ids"`
VerifierRunID AgentRunID `json:"verifier_run_id"`
VerifierIdentity string `json:"verifier_identity"`
EvidenceRefs []EvidenceRef `json:"evidence_refs"`
Summary string `json:"summary"`
State GraphSynthesisState `json:"state"`
}
@@ -0,0 +1,58 @@
BEGIN;
SET ROLE work_role;
CREATE TABLE IF NOT EXISTS work.graph_result_candidates (
result_candidate_id TEXT PRIMARY KEY,
graph_id TEXT NOT NULL,
graph_revision BIGINT NOT NULL,
node_id TEXT NOT NULL,
author_run_id TEXT NOT NULL,
artifact_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
evidence_summary TEXT NOT NULL,
summary TEXT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (result_candidate_id <> ''), CHECK (graph_id <> ''), CHECK (graph_revision > 0),
CHECK (node_id <> ''), CHECK (author_run_id <> ''), CHECK (evidence_summary <> ''), CHECK (summary <> ''),
FOREIGN KEY (graph_id, graph_revision) REFERENCES work.graphs(graph_id, revision)
);
CREATE TABLE IF NOT EXISTS work.graph_conflicts (
conflict_id TEXT PRIMARY KEY,
graph_id TEXT NOT NULL,
graph_revision BIGINT NOT NULL,
node_id TEXT NOT NULL,
candidate_ids TEXT[] NOT NULL,
resolved BOOLEAN NOT NULL DEFAULT FALSE,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (conflict_id <> ''), CHECK (graph_id <> ''), CHECK (graph_revision > 0), CHECK (node_id <> ''),
CHECK (cardinality(candidate_ids) >= 2),
FOREIGN KEY (graph_id, graph_revision) REFERENCES work.graphs(graph_id, revision)
);
CREATE TABLE IF NOT EXISTS work.graph_synthesis_candidates (
synthesis_id TEXT PRIMARY KEY,
graph_id TEXT NOT NULL,
graph_revision BIGINT NOT NULL,
node_id TEXT NOT NULL,
candidate_ids TEXT[] NOT NULL,
selected_candidate_id TEXT NOT NULL,
dissent_candidate_ids TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
verifier_run_id TEXT NOT NULL,
verifier_identity TEXT NOT NULL,
evidence_refs TEXT[] NOT NULL,
summary TEXT NOT NULL,
state TEXT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (synthesis_id <> ''), CHECK (graph_id <> ''), CHECK (graph_revision > 0), CHECK (node_id <> ''),
CHECK (cardinality(candidate_ids) >= 2), CHECK (selected_candidate_id <> ''), CHECK (verifier_run_id <> ''),
CHECK (verifier_identity <> ''), CHECK (cardinality(evidence_refs) >= 1), CHECK (summary <> ''), CHECK (state = 'VERIFIED'),
FOREIGN KEY (graph_id, graph_revision) REFERENCES work.graphs(graph_id, revision)
);
CREATE INDEX IF NOT EXISTS graph_result_candidates_node_idx ON work.graph_result_candidates(graph_id, graph_revision, node_id);
CREATE INDEX IF NOT EXISTS graph_conflicts_node_idx ON work.graph_conflicts(graph_id, graph_revision, node_id);
CREATE INDEX IF NOT EXISTS graph_synthesis_node_idx ON work.graph_synthesis_candidates(graph_id, graph_revision, node_id);
RESET ROLE;
COMMIT;
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000188-graph-synthesis-independent-verifier.json",
"task_id": "DEV-000188",
"task_file": "dev/tasks/DEV-000190-live-audit-status-sync.json",
"task_id": "DEV-000190",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000189",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "41bc4b5",
"risk_tier": "E",
"intent": "Persist Work Graph result candidates, conflicts and independently verified synthesis through the work owner and expose the governed synthesis runtime path.",
"requirement_ids": ["WP-403", "Durable graph result evidence", "Durable conflict resolution", "Synthesis runtime endpoint"],
"allowed_change_scope": {"modules": ["work-agent-execution", "work-storage", "work-graph-runtime"], "paths": ["contracts/work/graph.go", "database/migrations/036_work_graph_result_synthesis.sql", "adapters/postgres/work_repository.go", "modules/work/ports/graph.go", "modules/work/application/graph_runtime.go", "cmd/work-graph-runtime/main.go", "tests/agent_execution/graph_persistence_sql_test.go", "tests/agent_execution/graph_runtime_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000189-durable-graph-synthesis-runtime.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "contracts/authority/**", "modules/epistemics/**", "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": 1},
"contracts": {"consume": ["Existing Work Graph ResultCandidate/ConflictSet/GraphSynthesisCandidate contracts", "WP-403 independent verifier gate"], "may_change": ["GraphResult JSON serialization tags", "GraphStore persistence port"]},
"semantic_owner": "work-agent-execution",
"canonical_state": ["work.graph_result_candidates", "work.graph_conflicts", "work.graph_synthesis_candidates"],
"state_transitions": ["result candidate append -> conflict append -> independently verified synthesis append", "failed verification does not persist a resolved synthesis"],
"side_effects_and_privileges": ["Work schema writes only; no external mutation, provider call, secret expansion, or authority change"],
"persistence_and_migrations": ["Append-only migration 036; one owner-local transaction persists candidates, conflict and synthesis"],
"failure_retry_restart": ["Idempotent exact-record inserts; transaction rollback preserves no partial resolution; restart reloads durable evidence"],
"assumptions": ["Production migration runner applies migration before runtime endpoint use", "Authenticated upstream boundary already protects runtime access"],
"acceptance_scenarios": ["SQL is revision scoped and work-owned", "runtime rejects invalid/self-verifying synthesis", "successful synthesis persists all candidates, dissent and verifier identity atomically", "production canary returns durable success without external side effect"],
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -task dev/tasks/DEV-000189-durable-graph-synthesis-runtime.json", "go run ./cmd/devctl check-diff -task dev/tasks/DEV-000189-durable-graph-synthesis-runtime.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl independent", "git diff --check", "CI", "production migration/readiness and authenticated synthesis canary"],
"terminal_gap_states": ["BLOCKED_BY_MIGRATION_DECISION", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000190",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "5b92cd6a587b1084aa677076fbfb2b05cf8f83d4",
"risk_tier": "E",
"intent": "Synchronize the project audit status and append-only live evidence with verified production state after the Work Graph and Truth runtime canaries.",
"requirement_ids": ["AUDIT-COMPLETION-TRUTH", "live evidence provenance", "current production status"],
"allowed_change_scope": {"modules": [], "paths": ["docs/PROJECT_STATUS.md", "docs/evidence/audit-runtime-status-2026-08-23-v1.json", "docs/evidence/truth-runtime-canary-2026-08-23-v1.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000190-live-audit-status-sync.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "modules/**", "cmd/**", ".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 status contracts", "existing Truth bundle contract"], "may_change": ["audit status projections", "append-only live evidence records"]},
"semantic_owner": "project-governance",
"canonical_state": ["docs/PROJECT_STATUS.md", "docs/evidence/audit-runtime-status-2026-08-23-v1.json", "docs/evidence/truth-runtime-canary-2026-08-23-v1.json"],
"state_transitions": ["stale status projection -> current evidence-backed status"],
"side_effects_and_privileges": ["documentation and evidence only; no runtime mutation, secret access or deployment configuration change"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["uncertain or unavailable live evidence remains explicitly pending; no inferred success"],
"assumptions": ["production revision and canary identifiers are taken from the verified live checks recorded with this task"],
"acceptance_scenarios": ["status reports revision 5b92cd6", "fleet reports FAST selected with all four tiers ready", "agent worker reports recovery-only because bridge is absent", "Truth canary evidence records persisted PROVISIONAL/UNVERIFIED bundle", "authenticated UI and bridge gaps remain explicit"],
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -task dev/tasks/DEV-000190-live-audit-status-sync.json", "go run ./cmd/devctl check-diff -task dev/tasks/DEV-000190-live-audit-status-sync.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"]
}
+6 -5
View File
@@ -1,24 +1,25 @@
# Assistant project status
## Current audit delta — 2026-08-22
## Current audit delta — 2026-08-23
- `CURRENT_PRODUCTION_REVISION = 5960844fcb7b9bfba9c24e18a5d15b569d0d3a08`; public `/healthz` is healthy. Current runtime evidence: `docs/evidence/audit-runtime-status-2026-08-22-v4.json`.
- `CURRENT_PRODUCTION_REVISION = 5b92cd6a587b1084aa677076fbfb2b05cf8f83d4`; public `/healthz` is healthy. Current runtime evidence: `docs/evidence/audit-runtime-status-2026-08-23-v1.json`.
- `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`; after adapter timeout was extended and benchmark output capped at 256 bytes, all four cases still timed out at 60 seconds. Evidence: `docs/evidence/wp-200-medium-qualification-2026-08-22-v3.json`; no activation was attempted.
- `HEAVY_PROVIDER_STATUS = LIVE_QUALIFIED_UNACTIVATED`; discovery refreshed the stale GPU registry and current `gemma4:26b` passed all four bounded cases (`SUPPORTED`, 1000‰ quality, 0‰ critical failures). Evidence: `docs/evidence/wp-200-heavy-qualification-2026-08-22-v2.json`; binding remains INACTIVE and no activation was attempted.
- `AGENT_EXECUTION_STATUS = RECOVERY_ONLY`; optional `AGENT_EXECUTION_BRIDGE_URL` is wired into production compose but remains unset, and no Codex/bridge runtime is deployed. Durable recovery and status endpoints remain active.
- `FLEET_EXECUTION_EVIDENCE = docs/evidence/audit-runtime-status-2026-08-22-v4.json`; FAST, MEDIUM, HEAVY and LIFEBOAT are currently readiness-ready, selected tier is HEAVY, Heavy gemma4:26b is qualified but inactive, and Medium remains unqualified.
- `FLEET_EXECUTION_EVIDENCE = docs/evidence/audit-runtime-status-2026-08-23-v1.json`; FAST, MEDIUM, HEAVY and LIFEBOAT are readiness-ready, preferred and selected tier is FAST, and LIFEBOAT remains the fallback tier.
- `TRUTH_RESEARCH_STATUS = LIVE_CANARY_PERSISTED`; web research evaluated a public health source and persisted an OBSERVED/COMPLETE/NO-CONFLICT truth bundle; claim remains UNVERIFIED and belief PROVISIONAL. Evidence: `docs/evidence/truth-runtime-canary-2026-08-23-v1.json`.
- `BACKUP_RESTORE_EVIDENCE = docs/evidence/wp-002-postgres-restore-2026-08-22-v1.json`; latest backup checksum and isolated restore verified with 11 schemas and 60 tables.
- `UI_AUTHENTICATED_ACCEPTANCE = PENDING`; public login UI is reachable, but no authenticated browser session was available for cabinet visual acceptance.
- `SECURITY_NOTE = one production executor secret was rotated after an accidental diagnostic disclosure; values are not retained in repository evidence.`
Audit date: 2026-08-22. Historical governance checkpoint: DEV-000099 revision 1.
Audit date: 2026-08-23. Historical governance checkpoint: 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`
- `ACTIVE_TASK = dev/tasks/DEV-000190-live-audit-status-sync.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`
@@ -0,0 +1,15 @@
{
"captured_at": "2026-08-23",
"production_revision": "5b92cd6a587b1084aa677076fbfb2b05cf8f83d4",
"public_health": {"url": "https://assistant.cin.su/healthz", "status": "ok"},
"runtime_readiness": {
"core": "ready",
"research": "ready",
"work_graph": "ready",
"reflection_worker": "running",
"agent_worker": {"ready": false, "mode": "recovery-only", "executor_configured": false, "reason": "protected agent execution bridge URL is not configured"}
},
"fleet": {"ready_tiers": ["FAST", "MEDIUM", "HEAVY", "LIFEBOAT"], "preferred_tier": "FAST", "selected_tier": "FAST", "fallback_tier": "LIFEBOAT"},
"work_graph_canary": {"endpoint": "internal work-graph-runtime /v1/work-graphs/synthesis", "result": "HTTP 200; candidates, conflict and VERIFIED synthesis persisted"},
"interpretation": "Readiness and canary evidence prove runtime behavior only; they do not prove external Agent Execution bridge availability or authenticated UI acceptance."
}
@@ -0,0 +1,10 @@
{
"captured_at": "2026-08-23",
"runtime": "research-runtime",
"endpoint": "/v1/research/web/evaluate",
"plan_id": "live-truth-canary-20260822-2325",
"source_locator": "https://assistant.cin.su/healthz",
"result": {"finding_state": "SUPPORTED", "evidence": "OBSERVED", "coverage": "COMPLETE", "conflict": "NONE"},
"durable_persistence": {"truth_bundle": "persisted", "claim_verification": "UNVERIFIED", "belief_state": "PROVISIONAL"},
"interpretation": "The runtime preserves source-chain evidence and does not promote an unverified claim to a verified belief."
}
+22
View File
@@ -25,6 +25,28 @@ type GraphCompleteRequest struct {
Succeeded bool `json:"succeeded"`
}
type GraphSynthesisRequest struct {
Conflict work.ConflictSet `json:"conflict"`
Candidates []work.ResultCandidate `json:"candidates"`
SelectedCandidate string `json:"selected_candidate_id"`
VerifierRunID work.AgentRunID `json:"verifier_run_id"`
VerifierIdentity string `json:"verifier_identity"`
EvidenceRefs []work.EvidenceRef `json:"evidence_refs"`
Summary string `json:"summary"`
Verified bool `json:"verified"`
}
func SynthesizeGraph(ctx context.Context, store ports.GraphResultStore, request GraphSynthesisRequest) (work.ConflictSet, work.GraphSynthesisCandidate, error) {
conflict, synthesis, err := domain.BuildVerifiedGraphSynthesis(request.Conflict, request.Candidates, request.SelectedCandidate, request.VerifierRunID, request.VerifierIdentity, request.EvidenceRefs, request.Summary, request.Verified)
if err != nil {
return work.ConflictSet{}, work.GraphSynthesisCandidate{}, err
}
if err := store.SaveGraphSynthesis(ctx, conflict, request.Candidates, synthesis); err != nil {
return work.ConflictSet{}, work.GraphSynthesisCandidate{}, err
}
return conflict, synthesis, nil
}
func AdmitAndSchedule(ctx context.Context, store ports.GraphStore, request GraphScheduleRequest) (work.WorkGraph, []work.GraphNodeID, error) {
if request.Now < 1 {
request.Now = time.Now().UnixMilli()
+4
View File
@@ -12,3 +12,7 @@ type GraphStore interface {
SaveDispatchIntent(context.Context, work.AgentDispatchIntent) error
EnqueueDispatch(context.Context, work.AgentDispatchQueueItem) error
}
type GraphResultStore interface {
SaveGraphSynthesis(context.Context, work.ConflictSet, []work.ResultCandidate, work.GraphSynthesisCandidate) error
}
@@ -18,3 +18,16 @@ func TestGraphPersistenceMigrationIsRevisionScoped(t *testing.T) {
}
}
}
func TestGraphSynthesisMigrationIsWorkOwnedAndAtomicInputsExist(t *testing.T) {
data, err := os.ReadFile("../../database/migrations/036_work_graph_result_synthesis.sql")
if err != nil {
t.Fatal(err)
}
sql := string(data)
for _, marker := range []string{"work.graph_result_candidates", "work.graph_conflicts", "work.graph_synthesis_candidates", "verifier_run_id", "dissent_candidate_ids", "FOREIGN KEY (graph_id, graph_revision)", "SET ROLE work_role"} {
if !strings.Contains(sql, marker) {
t.Fatalf("graph synthesis persistence marker missing: %s", marker)
}
}
}
@@ -2,7 +2,9 @@ package agent_execution_test
import (
"context"
"encoding/json"
"strconv"
"strings"
"testing"
"git.cin.su/m/Assistant/contracts/work"
@@ -58,3 +60,37 @@ func TestGraphRuntimeSchedulesFanOutAndCompletesFanIn(t *testing.T) {
t.Fatalf("completed=%+v err=%v", completed, err)
}
}
type graphResultStoreStub struct {
conflict work.ConflictSet
count int
}
func (s *graphResultStoreStub) SaveGraphSynthesis(_ context.Context, conflict work.ConflictSet, candidates []work.ResultCandidate, _ work.GraphSynthesisCandidate) error {
s.conflict = conflict
s.count = len(candidates)
return nil
}
func TestSynthesizeGraphPersistsVerifiedDissent(t *testing.T) {
store := &graphResultStoreStub{}
conflict, synthesis, err := application.SynthesizeGraph(context.Background(), store, application.GraphSynthesisRequest{
Conflict: work.ConflictSet{ID: "conflict-1", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", CandidateIDs: []string{"candidate-a", "candidate-b"}},
Candidates: []work.ResultCandidate{{ID: "candidate-a", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", AuthorRunID: "producer-a", EvidenceSummary: "a", Summary: "a"}, {ID: "candidate-b", GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", AuthorRunID: "producer-b", EvidenceSummary: "b", Summary: "b"}},
SelectedCandidate: "candidate-a", VerifierRunID: "verifier-1", VerifierIdentity: "independent", EvidenceRefs: []work.EvidenceRef{"tests:1"}, Summary: "verified", Verified: true,
})
if err != nil || !conflict.Resolved || synthesis.State != work.GraphSynthesisVerified || store.count != 2 || !store.conflict.Resolved {
t.Fatalf("unexpected runtime synthesis: conflict=%+v synthesis=%+v stored=%+v err=%v", conflict, synthesis, store.conflict, err)
}
}
func TestGraphSynthesisJSONUsesStableSnakeCaseContract(t *testing.T) {
payload, err := json.Marshal(application.GraphSynthesisRequest{Conflict: work.ConflictSet{ID: "c", GraphID: "g", GraphRevision: 1, NodeID: "n", CandidateIDs: []string{"a", "b"}}, SelectedCandidate: "a", VerifierRunID: "v", VerifierIdentity: "independent", EvidenceRefs: []work.EvidenceRef{"e"}, Summary: "s", Verified: true})
if err != nil || !strings.Contains(string(payload), `"graph_id":"g"`) || !strings.Contains(string(payload), `"selected_candidate_id":"a"`) {
t.Fatalf("unexpected synthesis JSON: %s err=%v", payload, err)
}
var decoded application.GraphSynthesisRequest
if err := json.Unmarshal(payload, &decoded); err != nil || decoded.Conflict.GraphID != "g" || decoded.SelectedCandidate != "a" || !decoded.Verified {
t.Fatalf("snake_case request did not round-trip: %+v err=%v", decoded, err)
}
}