Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79c99289e7 | ||
|
|
07a8de9cb5 | ||
|
|
fdf38196f0 | ||
|
|
b753761e1a | ||
|
|
5faa5321dd | ||
|
|
7271d7950d | ||
|
|
533243da25 | ||
|
|
b1fba3ccef | ||
|
|
b0afa2417f | ||
|
|
6f5f7e1ee3 | ||
|
|
94c5bcb017 | ||
|
|
3f7c4705fe | ||
|
|
1ccd43c552 | ||
|
|
d38406fbc5 | ||
|
|
f73c69960c | ||
|
|
32e33e3050 | ||
|
|
e9bb42cd07 | ||
|
|
4a800df1e1 | ||
|
|
b2f922cbbc | ||
|
|
a4450348a1 | ||
|
|
323a67a7d5 | ||
|
|
bab96b7ede | ||
|
|
1b091a50f2 | ||
|
|
790fd7e768 | ||
|
|
875286b9fc | ||
|
|
8e26826ae2 | ||
|
|
c30e0c72ea | ||
|
|
d2338b1485 | ||
|
|
7f82240a8f | ||
|
|
fa62322ed2 | ||
|
|
8081b4de34 | ||
|
|
f792d208ef | ||
|
|
04dad79af1 | ||
|
|
02b472b627 | ||
|
|
a6eb4abc91 | ||
|
|
9766e5396e | ||
|
|
cb92f95550 | ||
|
|
e83cf3582b | ||
|
|
a65ee372a9 | ||
|
|
1f01b7013c | ||
|
|
93bc4a0fdf | ||
|
|
aec63de0c7 | ||
|
|
dc7f7b3b2b | ||
|
|
e3b55c9705 | ||
|
|
879de3bd95 | ||
|
|
3e0be6245e | ||
|
|
08deab8dda | ||
|
|
82ee6e5ba0 | ||
|
|
339f6a6770 | ||
|
|
6fb9184d14 | ||
|
|
3f2b008109 | ||
|
|
470fdf8c03 | ||
|
|
15c9517d0c | ||
|
|
8687c9b792 | ||
|
|
ff33a62edd | ||
|
|
b439a813e5 |
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@ package postgres
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -64,7 +65,7 @@ func (r *EpistemicsRepository) RecentReflectionCandidates(ctx context.Context, l
|
||||
}
|
||||
rows, err := r.pool.Query(ctx, `SELECT
|
||||
reflection_candidate_id,trigger,state,source_experience_id,original_intent_id,source_utterance_id,
|
||||
summary,evidence_refs,requires_cognition,recorded_at_unix_millis,revision
|
||||
summary,evidence_refs,requires_cognition,recorded_at_unix_millis,revision,claimed_by,claim_expires_at_unix_millis
|
||||
FROM epistemics.reflection_candidates ORDER BY recorded_at_unix_millis DESC, reflection_candidate_id DESC LIMIT $1`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -74,7 +75,7 @@ func (r *EpistemicsRepository) RecentReflectionCandidates(ctx context.Context, l
|
||||
for rows.Next() {
|
||||
var value epistemics.ReflectionCandidate
|
||||
if err := rows.Scan(&value.ID, &value.Trigger, &value.State, &value.SourceExperienceID, &value.OriginalIntentID, &value.SourceUtteranceID,
|
||||
&value.Summary, &value.EvidenceRefs, &value.RequiresCognition, &value.RecordedAtUnixMillis, &value.Revision); err != nil {
|
||||
&value.Summary, &value.EvidenceRefs, &value.RequiresCognition, &value.RecordedAtUnixMillis, &value.Revision, &value.ClaimedBy, &value.ClaimExpiresAtUnixMillis); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, value)
|
||||
@@ -82,6 +83,61 @@ func (r *EpistemicsRepository) RecentReflectionCandidates(ctx context.Context, l
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) ClaimReflectionCandidate(ctx context.Context, workerID string, nowUnixMillis, leaseMillis int64) (epistemics.ReflectionCandidate, error) {
|
||||
if workerID == "" || leaseMillis < 1 {
|
||||
return epistemics.ReflectionCandidate{}, fmt.Errorf("invalid reflection claim")
|
||||
}
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return epistemics.ReflectionCandidate{}, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
var value epistemics.ReflectionCandidate
|
||||
err = tx.QueryRow(ctx, `SELECT reflection_candidate_id,trigger,state,source_experience_id,original_intent_id,source_utterance_id,summary,evidence_refs,requires_cognition,recorded_at_unix_millis,revision,claimed_by,claim_expires_at_unix_millis
|
||||
FROM epistemics.reflection_candidates
|
||||
WHERE state='OPEN' AND (claim_expires_at_unix_millis=0 OR claim_expires_at_unix_millis < $1)
|
||||
ORDER BY recorded_at_unix_millis,reflection_candidate_id
|
||||
FOR UPDATE SKIP LOCKED LIMIT 1`, nowUnixMillis).Scan(&value.ID, &value.Trigger, &value.State, &value.SourceExperienceID, &value.OriginalIntentID, &value.SourceUtteranceID, &value.Summary, &value.EvidenceRefs, &value.RequiresCognition, &value.RecordedAtUnixMillis, &value.Revision, &value.ClaimedBy, &value.ClaimExpiresAtUnixMillis)
|
||||
if err != nil {
|
||||
return epistemics.ReflectionCandidate{}, err
|
||||
}
|
||||
value.State = epistemics.ReflectionCandidateEvaluating
|
||||
value.ClaimedBy = workerID
|
||||
value.ClaimExpiresAtUnixMillis = nowUnixMillis + leaseMillis
|
||||
_, err = tx.Exec(ctx, `UPDATE epistemics.reflection_candidates SET state=$1,claimed_by=$2,claim_expires_at_unix_millis=$3 WHERE reflection_candidate_id=$4 AND state='OPEN'`, value.State, value.ClaimedBy, value.ClaimExpiresAtUnixMillis, value.ID)
|
||||
if err != nil {
|
||||
return epistemics.ReflectionCandidate{}, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return epistemics.ReflectionCandidate{}, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) RecoverReflectionClaims(ctx context.Context, nowUnixMillis int64) (int64, error) {
|
||||
result, err := r.pool.Exec(ctx, `UPDATE epistemics.reflection_candidates SET state='OPEN',claimed_by='',claim_expires_at_unix_millis=0 WHERE state='EVALUATING' AND claim_expires_at_unix_millis > 0 AND claim_expires_at_unix_millis < $1`, nowUnixMillis)
|
||||
return result.RowsAffected(), err
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) ResolveReflectionCandidate(ctx context.Context, id epistemics.ReflectionCandidateID, workerID string, nowUnixMillis int64) error {
|
||||
if id == "" || workerID == "" {
|
||||
return fmt.Errorf("invalid reflection resolution")
|
||||
}
|
||||
result, err := r.pool.Exec(ctx, `UPDATE epistemics.reflection_candidates SET state='RESOLVED',claimed_by='',claim_expires_at_unix_millis=0,revision=revision+1 WHERE reflection_candidate_id=$1 AND state='EVALUATING' AND claimed_by=$2 AND claim_expires_at_unix_millis >= $3`, id, workerID, nowUnixMillis)
|
||||
if err == nil && result.RowsAffected() != 1 {
|
||||
return fmt.Errorf("reflection candidate claim lost")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) WaitForReflectionCandidate(ctx context.Context, interval time.Duration) error {
|
||||
if interval <= 0 {
|
||||
return fmt.Errorf("invalid reflection poll interval")
|
||||
}
|
||||
_, err := r.pool.Exec(ctx, `SELECT pg_sleep($1)`, interval.Seconds())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) SaveExperienceRecord(ctx context.Context, value epistemics.ExperienceRecord) error {
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO epistemics.experience_records (
|
||||
experience_id, source_kind, original_intent_id, source_utterance_id, context_refs, help_method, help_method_ref,
|
||||
|
||||
@@ -2,14 +2,23 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
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 +48,198 @@ 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) LoadDispatchIntent(ctx context.Context, dispatchID string) (work.AgentDispatchIntent, error) {
|
||||
var intent work.AgentDispatchIntent
|
||||
err := r.pool.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 dispatch_id=$1`, dispatchID).Scan(&intent.DispatchID, &intent.IdempotencyKey, &intent.DelegationID, &intent.RunID, &intent.ImplementationID, &intent.ImplementationRevision, &intent.SourceRevision, &intent.WorktreeID, &intent.VerificationPlanRef, &intent.CreatedAtUnixMillis)
|
||||
if err != nil {
|
||||
return work.AgentDispatchIntent{}, fmt.Errorf("load dispatch intent: %w", err)
|
||||
}
|
||||
return intent, nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) EnqueueDispatch(ctx context.Context, item work.AgentDispatchQueueItem) error {
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO work.agent_dispatch_queue(dispatch_id,state,available_at_unix_millis,attempts,claimed_by,updated_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6) ON CONFLICT(dispatch_id) DO NOTHING`, item.DispatchID, item.State, item.AvailableAtUnixMillis, item.Attempts, item.ClaimedBy, item.UpdatedAtUnixMillis)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorkRepository) ClaimNextDispatch(ctx context.Context, workerID string, nowUnixMillis int64) (work.AgentDispatchQueueItem, error) {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return work.AgentDispatchQueueItem{}, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
var item work.AgentDispatchQueueItem
|
||||
err = tx.QueryRow(ctx, `SELECT dispatch_id,state,available_at_unix_millis,attempts,claimed_by,updated_at_unix_millis FROM work.agent_dispatch_queue WHERE state='QUEUED' AND available_at_unix_millis <= $1 ORDER BY available_at_unix_millis,dispatch_id FOR UPDATE SKIP LOCKED LIMIT 1`, nowUnixMillis).Scan(&item.DispatchID, &item.State, &item.AvailableAtUnixMillis, &item.Attempts, &item.ClaimedBy, &item.UpdatedAtUnixMillis)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return work.AgentDispatchQueueItem{}, nil
|
||||
}
|
||||
return work.AgentDispatchQueueItem{}, err
|
||||
}
|
||||
item.State = work.AgentDispatchClaimed
|
||||
item.Attempts++
|
||||
item.ClaimedBy = workerID
|
||||
item.UpdatedAtUnixMillis = nowUnixMillis
|
||||
_, err = tx.Exec(ctx, `UPDATE work.agent_dispatch_queue SET state=$1,attempts=$2,claimed_by=$3,updated_at_unix_millis=$4 WHERE dispatch_id=$5 AND state='QUEUED'`, item.State, item.Attempts, item.ClaimedBy, item.UpdatedAtUnixMillis, item.DispatchID)
|
||||
if err != nil {
|
||||
return work.AgentDispatchQueueItem{}, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return work.AgentDispatchQueueItem{}, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) WaitForDispatch(ctx context.Context, interval time.Duration) error {
|
||||
if interval <= 0 {
|
||||
return fmt.Errorf("invalid worker poll interval")
|
||||
}
|
||||
_, err := r.pool.Exec(ctx, `SELECT pg_sleep($1)`, interval.Seconds())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WorkRepository) CompleteDispatch(ctx context.Context, dispatchID, workerID string, state work.AgentDispatchQueueState, nowUnixMillis int64) error {
|
||||
if state != work.AgentDispatchCompleted && state != work.AgentDispatchFailed {
|
||||
return fmt.Errorf("invalid terminal dispatch state: %s", state)
|
||||
}
|
||||
command, err := r.pool.Exec(ctx, `UPDATE work.agent_dispatch_queue SET state=$1,updated_at_unix_millis=$2 WHERE dispatch_id=$3 AND state='CLAIMED' AND claimed_by=$4`, state, nowUnixMillis, dispatchID, workerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if command.RowsAffected() != 1 {
|
||||
return ErrAgentCallbackRejected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) SaveWorkGraph(ctx context.Context, graph work.WorkGraph) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO work.graphs(graph_id,revision,state,max_parallel,max_attempts) VALUES($1,$2,$3,$4,$5) ON CONFLICT(graph_id,revision) DO NOTHING`, graph.ID, graph.Revision, graph.State, graph.Budget.MaxParallel, graph.Budget.MaxAttempts); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, node := range graph.Nodes {
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO work.graph_nodes(graph_id,revision,node_id,idempotency_key,state,attempts) VALUES($1,$2,$3,$4,$5,$6) ON CONFLICT(graph_id,revision,node_id) DO NOTHING`, graph.ID, graph.Revision, node.ID, node.IdempotencyKey, node.State, node.Attempts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, edge := range graph.Edges {
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO work.graph_edges(graph_id,revision,from_node_id,to_node_id) VALUES($1,$2,$3,$4) ON CONFLICT(graph_id,revision,from_node_id,to_node_id) DO NOTHING`, graph.ID, graph.Revision, edge.From, edge.To); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *WorkRepository) LoadWorkGraph(ctx context.Context, graphID string, revision int64) (work.WorkGraph, error) {
|
||||
var graph work.WorkGraph
|
||||
err := r.pool.QueryRow(ctx, `SELECT graph_id,revision,state,max_parallel,max_attempts FROM work.graphs WHERE graph_id=$1 AND revision=$2`, graphID, revision).Scan(&graph.ID, &graph.Revision, &graph.State, &graph.Budget.MaxParallel, &graph.Budget.MaxAttempts)
|
||||
if err != nil {
|
||||
return work.WorkGraph{}, fmt.Errorf("load graph: %w", err)
|
||||
}
|
||||
rows, err := r.pool.Query(ctx, `SELECT node_id,idempotency_key,state,attempts FROM work.graph_nodes WHERE graph_id=$1 AND revision=$2 ORDER BY node_id`, graphID, revision)
|
||||
if err != nil {
|
||||
return work.WorkGraph{}, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var node work.GraphNode
|
||||
if err := rows.Scan(&node.ID, &node.IdempotencyKey, &node.State, &node.Attempts); err != nil {
|
||||
rows.Close()
|
||||
return work.WorkGraph{}, err
|
||||
}
|
||||
graph.Nodes = append(graph.Nodes, node)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
rows.Close()
|
||||
return work.WorkGraph{}, err
|
||||
}
|
||||
rows.Close()
|
||||
edges, err := r.pool.Query(ctx, `SELECT from_node_id,to_node_id FROM work.graph_edges WHERE graph_id=$1 AND revision=$2 ORDER BY from_node_id,to_node_id`, graphID, revision)
|
||||
if err != nil {
|
||||
return work.WorkGraph{}, err
|
||||
}
|
||||
for edges.Next() {
|
||||
var edge work.GraphEdge
|
||||
if err := edges.Scan(&edge.From, &edge.To); err != nil {
|
||||
edges.Close()
|
||||
return work.WorkGraph{}, err
|
||||
}
|
||||
graph.Edges = append(graph.Edges, edge)
|
||||
}
|
||||
if err := edges.Err(); err != nil {
|
||||
edges.Close()
|
||||
return work.WorkGraph{}, err
|
||||
}
|
||||
edges.Close()
|
||||
return graph, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return 0, err
|
||||
}
|
||||
return command.RowsAffected(), nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/adapters/postgres"
|
||||
secretcontracts "git.cin.su/m/Assistant/contracts/secrets"
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/application"
|
||||
platformsecrets "git.cin.su/m/Assistant/platform/secrets"
|
||||
)
|
||||
|
||||
func main() {
|
||||
workerID := flag.String("worker-id", "agent-worker-1", "durable worker identity")
|
||||
staleAfter := flag.Duration("stale-after", 10*time.Minute, "age after which a claimed item is recovered")
|
||||
leaseTTL := flag.Duration("lease-ttl", 15*time.Minute, "dry-run lease duration")
|
||||
pollInterval := flag.Duration("poll-interval", 5*time.Second, "database-owned idle wake-up interval")
|
||||
once := flag.Bool("once", false, "run one cycle and exit")
|
||||
flag.Parse()
|
||||
|
||||
password, err := resolveDatabasePassword("WORK_POSTGRES_PASSWORD", "WORK_POSTGRES_CREDENTIAL_REF", secretcontracts.RuntimeConsumerWorkWorker)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
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: password})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer repository.Close()
|
||||
ctx := context.Background()
|
||||
for {
|
||||
result, cycleErr := application.ExecuteWorkerCycle(ctx, repository, *workerID, work.AgentFencingToken(1), time.Now().UTC(), *staleAfter, *leaseTTL)
|
||||
if cycleErr != nil {
|
||||
fmt.Fprintln(os.Stderr, cycleErr)
|
||||
if *once {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if cycleErr == nil && (result.Completed || result.RecoveredClaims > 0) {
|
||||
_ = json.NewEncoder(os.Stdout).Encode(result)
|
||||
}
|
||||
if *once {
|
||||
return
|
||||
}
|
||||
if err := repository.WaitForDispatch(ctx, *pollInterval); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveDatabasePassword(name, refName string, consumer secretcontracts.RuntimeConsumer) (string, error) {
|
||||
if _, ok := os.LookupEnv(name); ok {
|
||||
return "", fmt.Errorf("%s direct environment secret is forbidden", name)
|
||||
}
|
||||
path, ref := os.Getenv(name+"_FILE"), os.Getenv(refName)
|
||||
if path == "" || ref == "" {
|
||||
return "", fmt.Errorf("%s_FILE and %s are required", name, refName)
|
||||
}
|
||||
root := os.Getenv("SECRET_BACKEND_ROOT")
|
||||
if root == "" {
|
||||
root = "/run/secrets"
|
||||
}
|
||||
resolver, err := platformsecrets.NewFileResolver(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: consumer, Path: path})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/adapters/postgres"
|
||||
secretcontracts "git.cin.su/m/Assistant/contracts/secrets"
|
||||
platformsecrets "git.cin.su/m/Assistant/platform/secrets"
|
||||
)
|
||||
|
||||
func main() {
|
||||
workerID := flag.String("worker-id", "reflection-worker-1", "stable worker identity")
|
||||
lease := flag.Duration("lease-ttl", 5*time.Minute, "candidate claim lease")
|
||||
pollInterval := flag.Duration("poll-interval", 15*time.Second, "database-owned idle wake-up interval")
|
||||
once := flag.Bool("once", false, "run one cycle and exit")
|
||||
flag.Parse()
|
||||
ctx := context.Background()
|
||||
password, err := resolveDatabasePassword("EPISTEMICS_POSTGRES_PASSWORD", "EPISTEMICS_POSTGRES_CREDENTIAL_REF")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
config := postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("EPISTEMICS_POSTGRES_USER"), Password: password}
|
||||
repository, err := postgres.OpenEpistemics(ctx, config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer repository.Close()
|
||||
for {
|
||||
now := time.Now().UnixMilli()
|
||||
if _, err := repository.RecoverReflectionClaims(ctx, now); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
candidate, claimErr := repository.ClaimReflectionCandidate(ctx, *workerID, now, lease.Milliseconds())
|
||||
if claimErr == nil {
|
||||
// This worker only closes the durable evaluation loop. Any cognitive
|
||||
// proposal remains a candidate and must be admitted by a separate governed flow.
|
||||
if err := repository.ResolveReflectionCandidate(ctx, candidate.ID, *workerID, time.Now().UnixMilli()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("resolved reflection candidate %s\n", candidate.ID)
|
||||
} else if *once {
|
||||
fmt.Println("no reflection candidate")
|
||||
}
|
||||
if *once {
|
||||
return
|
||||
}
|
||||
if err := repository.WaitForReflectionCandidate(ctx, *pollInterval); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveDatabasePassword(name, refName string) (string, error) {
|
||||
if _, ok := os.LookupEnv(name); ok {
|
||||
return "", fmt.Errorf("%s direct environment secret is forbidden", name)
|
||||
}
|
||||
path, ref := os.Getenv(name+"_FILE"), os.Getenv(refName)
|
||||
if path == "" || ref == "" {
|
||||
return "", fmt.Errorf("%s_FILE and %s are required", name, refName)
|
||||
}
|
||||
root := os.Getenv("SECRET_BACKEND_ROOT")
|
||||
if root == "" {
|
||||
root = "/run/secrets"
|
||||
}
|
||||
resolver, err := platformsecrets.NewFileResolver(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: secretcontracts.RuntimeConsumerEpistemicsWorker, Path: path})
|
||||
}
|
||||
@@ -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,25 @@
|
||||
package capability
|
||||
|
||||
type FleetTier string
|
||||
|
||||
const (
|
||||
FleetTierFast FleetTier = "FAST"
|
||||
FleetTierMedium FleetTier = "MEDIUM"
|
||||
FleetTierHeavy FleetTier = "HEAVY"
|
||||
FleetTierLifeboat FleetTier = "LIFEBOAT"
|
||||
)
|
||||
|
||||
type FleetMember struct {
|
||||
ProviderInstanceID ProviderInstanceID
|
||||
ModelDescriptorID ModelDescriptorID
|
||||
Tier FleetTier
|
||||
Ready bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
type FleetReadiness struct {
|
||||
ObservedAtUnixMillis int64
|
||||
Members []FleetMember
|
||||
ReadyTiers []FleetTier
|
||||
FallbackTier FleetTier
|
||||
}
|
||||
@@ -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,145 @@
|
||||
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
|
||||
type ResearchFindingState 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"
|
||||
FindingSupported ResearchFindingState = "SUPPORTED"
|
||||
FindingStale ResearchFindingState = "STALE"
|
||||
FindingConflicted ResearchFindingState = "CONFLICTED"
|
||||
FindingUnknown ResearchFindingState = "UNKNOWN"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ResearchCandidate is read-only evidence returned by a connector. The
|
||||
// connector supplies the immutable snapshot and exact excerpt; the evaluator
|
||||
// never fetches, rewrites or invents either one.
|
||||
type ResearchCandidate struct {
|
||||
PropositionKey string
|
||||
Statement string
|
||||
Source Source
|
||||
Snapshot SourceSnapshot
|
||||
Excerpt SourceExcerpt
|
||||
Evidence EvidenceKind
|
||||
Coverage CoverageState
|
||||
Conflict ConflictState
|
||||
}
|
||||
|
||||
type ResearchFinding struct {
|
||||
PropositionKey string
|
||||
Statement string
|
||||
Claim Claim
|
||||
Citations []Citation
|
||||
State ResearchFindingState
|
||||
Reason string
|
||||
}
|
||||
|
||||
type ResearchResult struct {
|
||||
PlanID ResearchPlanID
|
||||
Findings []ResearchFinding
|
||||
EvaluatedAtUnixMillis 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,38 @@
|
||||
package epistemics
|
||||
|
||||
type BeliefState string
|
||||
|
||||
const (
|
||||
BeliefProvisional BeliefState = "PROVISIONAL"
|
||||
BeliefConflicted BeliefState = "CONFLICTED"
|
||||
BeliefVerified BeliefState = "VERIFIED"
|
||||
)
|
||||
|
||||
type Observation struct {
|
||||
ID ObservationID
|
||||
SourceSnapshot SourceSnapshotID
|
||||
Excerpt SourceExcerptID
|
||||
Text string
|
||||
ObservedAt int64
|
||||
}
|
||||
|
||||
type Claim struct {
|
||||
ID ClaimID
|
||||
Statement string
|
||||
ObservationID ObservationID
|
||||
CitationID CitationID
|
||||
Verification VerificationState
|
||||
}
|
||||
|
||||
type Belief struct {
|
||||
ID BeliefID
|
||||
ClaimID ClaimID
|
||||
State BeliefState
|
||||
}
|
||||
|
||||
type TruthBundle struct {
|
||||
Observation Observation
|
||||
Claim Claim
|
||||
Belief Belief
|
||||
Citation Citation
|
||||
}
|
||||
@@ -120,15 +120,17 @@ type ExperienceRecord struct {
|
||||
// Cognitive reflection, if used, must go through Cognitive Gateway/Router and
|
||||
// produce candidates only; it cannot directly rewrite canonical state.
|
||||
type ReflectionCandidate struct {
|
||||
ID ReflectionCandidateID
|
||||
Trigger ReflectionTriggerKind
|
||||
State ReflectionCandidateState
|
||||
SourceExperienceID ExperienceRecordID
|
||||
OriginalIntentID OriginalIntentRef
|
||||
SourceUtteranceID SourceUtteranceRef
|
||||
Summary string
|
||||
EvidenceRefs []EvidenceRef
|
||||
RequiresCognition bool
|
||||
RecordedAtUnixMillis int64
|
||||
Revision int64
|
||||
ID ReflectionCandidateID
|
||||
Trigger ReflectionTriggerKind
|
||||
State ReflectionCandidateState
|
||||
SourceExperienceID ExperienceRecordID
|
||||
OriginalIntentID OriginalIntentRef
|
||||
SourceUtteranceID SourceUtteranceRef
|
||||
Summary string
|
||||
EvidenceRefs []EvidenceRef
|
||||
RequiresCognition bool
|
||||
RecordedAtUnixMillis int64
|
||||
Revision int64
|
||||
ClaimedBy string
|
||||
ClaimExpiresAtUnixMillis int64
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ const (
|
||||
type RuntimeConsumer string
|
||||
|
||||
const (
|
||||
RuntimeConsumerCore RuntimeConsumer = "core-runtime"
|
||||
RuntimeConsumerCore RuntimeConsumer = "core-runtime"
|
||||
RuntimeConsumerWorkWorker RuntimeConsumer = "agent-worker"
|
||||
RuntimeConsumerEpistemicsWorker RuntimeConsumer = "reflection-worker"
|
||||
)
|
||||
|
||||
// SecretFileRef is bootstrap delivery metadata for the V1 protected local
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package work
|
||||
|
||||
type AgentDispatchQueueState string
|
||||
|
||||
const (
|
||||
AgentDispatchQueued AgentDispatchQueueState = "QUEUED"
|
||||
AgentDispatchClaimed AgentDispatchQueueState = "CLAIMED"
|
||||
AgentDispatchCompleted AgentDispatchQueueState = "COMPLETED"
|
||||
AgentDispatchFailed AgentDispatchQueueState = "FAILED"
|
||||
)
|
||||
|
||||
type AgentDispatchQueueItem struct {
|
||||
DispatchID string
|
||||
State AgentDispatchQueueState
|
||||
AvailableAtUnixMillis int64
|
||||
Attempts int
|
||||
ClaimedBy string
|
||||
UpdatedAtUnixMillis int64
|
||||
}
|
||||
@@ -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,72 @@
|
||||
package work
|
||||
|
||||
type GraphNodeID string
|
||||
|
||||
type GraphState string
|
||||
type GraphNodeState string
|
||||
|
||||
const (
|
||||
GraphDraft GraphState = "DRAFT"
|
||||
GraphAdmitted GraphState = "ADMITTED"
|
||||
GraphRunning GraphState = "RUNNING"
|
||||
GraphPartial GraphState = "PARTIAL"
|
||||
GraphVerifying GraphState = "VERIFYING"
|
||||
GraphCompleted GraphState = "COMPLETED"
|
||||
GraphFailed GraphState = "FAILED"
|
||||
GraphUnknown GraphState = "UNKNOWN"
|
||||
|
||||
GraphNodeBlocked GraphNodeState = "BLOCKED_PREREQUISITE"
|
||||
GraphNodeReady GraphNodeState = "READY"
|
||||
GraphNodeLeased GraphNodeState = "LEASED"
|
||||
GraphNodeRunning GraphNodeState = "RUNNING"
|
||||
GraphNodeSuccess GraphNodeState = "SUCCEEDED"
|
||||
GraphNodeFailed GraphNodeState = "FAILED"
|
||||
GraphNodeCancel GraphNodeState = "CANCELLED"
|
||||
GraphNodeUnknown GraphNodeState = "UNKNOWN"
|
||||
)
|
||||
|
||||
type GraphBudget struct {
|
||||
MaxParallel int
|
||||
MaxAttempts int
|
||||
}
|
||||
|
||||
type GraphNode struct {
|
||||
ID GraphNodeID
|
||||
IdempotencyKey string
|
||||
State GraphNodeState
|
||||
Attempts int
|
||||
}
|
||||
|
||||
type GraphEdge struct {
|
||||
From GraphNodeID
|
||||
To GraphNodeID
|
||||
}
|
||||
|
||||
type WorkGraph struct {
|
||||
ID string
|
||||
Revision int64
|
||||
State GraphState
|
||||
Nodes []GraphNode
|
||||
Edges []GraphEdge
|
||||
Budget GraphBudget
|
||||
}
|
||||
|
||||
type ResultCandidate struct {
|
||||
ID string
|
||||
GraphID string
|
||||
GraphRevision int64
|
||||
NodeID GraphNodeID
|
||||
AuthorRunID AgentRunID
|
||||
ArtifactRefs []EvidenceRef
|
||||
EvidenceSummary string
|
||||
Summary string
|
||||
}
|
||||
|
||||
type ConflictSet struct {
|
||||
ID string
|
||||
GraphID string
|
||||
GraphRevision int64
|
||||
NodeID GraphNodeID
|
||||
CandidateIDs []string
|
||||
Resolved bool
|
||||
}
|
||||
@@ -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,29 @@
|
||||
package work
|
||||
|
||||
type DevelopmentStage string
|
||||
|
||||
const (
|
||||
DevelopmentEvidenceCollected DevelopmentStage = "EVIDENCE_COLLECTED"
|
||||
DevelopmentProposalCreated DevelopmentStage = "PROPOSAL_CREATED"
|
||||
DevelopmentTaskAdmitted DevelopmentStage = "TASK_ADMITTED"
|
||||
DevelopmentIsolated DevelopmentStage = "ISOLATED"
|
||||
DevelopmentVerified DevelopmentStage = "VERIFIED"
|
||||
DevelopmentCanary DevelopmentStage = "CANARY"
|
||||
DevelopmentPromoted DevelopmentStage = "PROMOTED"
|
||||
DevelopmentRolledBack DevelopmentStage = "ROLLED_BACK"
|
||||
)
|
||||
|
||||
type DevelopmentGate struct {
|
||||
ID string
|
||||
Stage DevelopmentStage
|
||||
EvidenceRefs []string
|
||||
ProposalRef string
|
||||
TaskPacketRef string
|
||||
WorktreeRef string
|
||||
ProducerRunRefs []AgentRunID
|
||||
VerifierRunRefs []AgentRunID
|
||||
VerificationEvidence string
|
||||
CanaryEvidence string
|
||||
PromotionRef string
|
||||
RollbackRef string
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,22 @@
|
||||
BEGIN;
|
||||
|
||||
SET ROLE work_role;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.agent_dispatch_queue (
|
||||
dispatch_id TEXT PRIMARY KEY REFERENCES work.agent_dispatch_intents(dispatch_id),
|
||||
state TEXT NOT NULL,
|
||||
available_at_unix_millis BIGINT NOT NULL,
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
claimed_by TEXT NOT NULL DEFAULT '',
|
||||
updated_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (state IN ('QUEUED','CLAIMED','COMPLETED','FAILED')),
|
||||
CHECK (available_at_unix_millis > 0),
|
||||
CHECK (attempts >= 0),
|
||||
CHECK (updated_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS agent_dispatch_queue_claim_idx
|
||||
ON work.agent_dispatch_queue(state, available_at_unix_millis, dispatch_id);
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,41 @@
|
||||
BEGIN;
|
||||
|
||||
SET ROLE work_role;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.graphs (
|
||||
graph_id TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
max_parallel INTEGER NOT NULL,
|
||||
max_attempts INTEGER NOT NULL,
|
||||
PRIMARY KEY (graph_id, revision),
|
||||
CHECK (graph_id <> ''), CHECK (revision > 0),
|
||||
CHECK (state IN ('DRAFT','ADMITTED','RUNNING','PAUSED','CANCELLING','PARTIAL','VERIFYING','COMPLETED','FAILED','UNKNOWN','RECOVERING')),
|
||||
CHECK (max_parallel > 0), CHECK (max_attempts > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.graph_nodes (
|
||||
graph_id TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL,
|
||||
node_id TEXT NOT NULL,
|
||||
idempotency_key TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
attempts INTEGER NOT NULL,
|
||||
PRIMARY KEY (graph_id, revision, node_id),
|
||||
FOREIGN KEY (graph_id, revision) REFERENCES work.graphs(graph_id, revision),
|
||||
CHECK (node_id <> ''), CHECK (idempotency_key <> ''), CHECK (attempts >= 0),
|
||||
CHECK (state IN ('BLOCKED_PREREQUISITE','READY','LEASED','RUNNING','SUCCEEDED','FAILED','CANCELLED','UNKNOWN','RECOVERING'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work.graph_edges (
|
||||
graph_id TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL,
|
||||
from_node_id TEXT NOT NULL,
|
||||
to_node_id TEXT NOT NULL,
|
||||
PRIMARY KEY (graph_id, revision, from_node_id, to_node_id),
|
||||
FOREIGN KEY (graph_id, revision) REFERENCES work.graphs(graph_id, revision),
|
||||
CHECK (from_node_id <> ''), CHECK (to_node_id <> ''), CHECK (from_node_id <> to_node_id)
|
||||
);
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,19 @@
|
||||
BEGIN;
|
||||
|
||||
SET ROLE epistemics_role;
|
||||
|
||||
ALTER TABLE epistemics.reflection_candidates
|
||||
ADD COLUMN IF NOT EXISTS claimed_by TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS claim_expires_at_unix_millis BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE epistemics.reflection_candidates
|
||||
DROP CONSTRAINT IF EXISTS reflection_candidates_claim_expiry_check;
|
||||
ALTER TABLE epistemics.reflection_candidates
|
||||
ADD CONSTRAINT reflection_candidates_claim_expiry_check
|
||||
CHECK (claim_expires_at_unix_millis >= 0);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS reflection_candidates_claimable_idx
|
||||
ON epistemics.reflection_candidates (state, claim_expires_at_unix_millis, recorded_at_unix_millis, reflection_candidate_id);
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -84,6 +84,51 @@ 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 } }
|
||||
|
||||
agent-worker:
|
||||
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: agent-worker, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
|
||||
restart: unless-stopped
|
||||
command: ["-worker-id", "agent-worker-production"]
|
||||
environment:
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_DATABASE: ${POSTGRES_DB:-assistant}
|
||||
WORK_POSTGRES_USER: work_role
|
||||
WORK_POSTGRES_CREDENTIAL_REF: credential://domovoy/work/db-password/v1
|
||||
WORK_POSTGRES_PASSWORD_FILE: /run/secrets/work_db_password
|
||||
SECRET_BACKEND_ROOT: /run/secrets
|
||||
depends_on: { postgres: { condition: service_healthy } }
|
||||
volumes:
|
||||
- assistant_core_secrets:/run/secrets:ro
|
||||
|
||||
reflection-worker:
|
||||
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: reflection-worker, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
|
||||
restart: unless-stopped
|
||||
command: ["-worker-id", "reflection-worker-production"]
|
||||
environment:
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_DATABASE: ${POSTGRES_DB:-assistant}
|
||||
EPISTEMICS_POSTGRES_USER: epistemics_role
|
||||
EPISTEMICS_POSTGRES_CREDENTIAL_REF: credential://domovoy/epistemics/db-password/v1
|
||||
EPISTEMICS_POSTGRES_PASSWORD_FILE: /run/secrets/epistemics_db_password
|
||||
SECRET_BACKEND_ROOT: /run/secrets
|
||||
depends_on: { postgres: { condition: service_healthy } }
|
||||
volumes:
|
||||
- assistant_core_secrets:/run/secrets:ro
|
||||
|
||||
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-000140-research-evaluation-runtime.json",
|
||||
"task_id": "DEV-000140",
|
||||
"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,23 @@
|
||||
{
|
||||
"task_id": "DEV-000129",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "bab96b7",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add durable Work-owned dispatch queue with atomic claim, retry attempt count and terminal state transitions as the first real dispatcher runtime foundation.",
|
||||
"requirement_ids": ["WP-301", "Durable dispatcher queue", "Atomic claim"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "work-storage"], "paths": ["database/migrations/030_work_agent_dispatch_queue.sql", "contracts/work/dispatch_queue.go", "adapters/postgres/work_repository.go", "tests/agent_execution/dispatch_queue_sql_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000129-agent-dispatch-queue.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", ".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-000121 durable persistence", "DEV-000120 dispatch intent"], "may_change": ["Work dispatch queue contract and repository methods"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["work.agent_dispatch_queue"],
|
||||
"state_transitions": ["QUEUED -> CLAIMED -> COMPLETED/FAILED"],
|
||||
"side_effects_and_privileges": ["Work schema only; no cross-owner SQL; no runtime entrypoint"],
|
||||
"persistence_and_migrations": ["Append-only migration 030; work_role only; production apply is a later canary step"],
|
||||
"failure_retry_restart": ["SKIP LOCKED prevents duplicate claim; attempts are durable; terminal transition is worker-fenced"],
|
||||
"assumptions": ["A later worker task will enqueue intents and execute claimed work"],
|
||||
"acceptance_scenarios": ["Queue schema is durable", "Concurrent claim uses row lock and SKIP LOCKED", "Wrong worker cannot complete another claim"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "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-000130",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "323a67a",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement a one-shot durable agent worker cycle that recovers stale claims, claims one queue item, executes the deterministic worker path and completes it with worker fencing.",
|
||||
"requirement_ids": ["WP-301", "Agent worker", "Stale-run/claim recovery"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "work-storage"], "paths": ["adapters/postgres/work_repository.go", "modules/work/ports/worker.go", "modules/work/application/worker.go", "cmd/agent-worker/main.go", "tests/agent_execution/worker_sql_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000130-agent-worker-recovery.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "runtime/**", ".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-000129 durable queue", "DEV-000122 deterministic worker", "DEV-000123 orchestration"], "may_change": ["WorkerStore port and one-shot worker application"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["Queue state and dispatch intent remain Work-owned"],
|
||||
"state_transitions": ["stale CLAIMED -> QUEUED -> CLAIMED -> COMPLETED/FAILED"],
|
||||
"side_effects_and_privileges": ["Explicit one-shot command; no unmanaged goroutine or in-memory queue"],
|
||||
"persistence_and_migrations": ["Reuses migration 030; production apply follows separate canary task"],
|
||||
"failure_retry_restart": ["Stale recovery is durable; worker completion requires claimed_by match; failures become FAILED"],
|
||||
"assumptions": ["Dispatcher enqueue path is supplied by a later integration task", "Deterministic worker remains the only implementation in this canary stage"],
|
||||
"acceptance_scenarios": ["Worker recovers stale claims", "Worker claim is row-locked", "Wrong worker cannot complete claim", "Worker cycle is one-shot and restartable"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "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-000131",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "a445034",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement the first deterministic Multi-Agent Work Graph harness: typed graph revision, DAG validation, budgets and dependency-aware ready-node selection.",
|
||||
"requirement_ids": ["WP-400", "DAG validation", "Fan-out/fan-in foundation"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["contracts/work/graph.go", "modules/work/domain/graph.go", "tests/agent_execution/graph_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000131-work-graph-harness.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "adapters/**", "cmd/**", "runtime/**", ".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-0021 graph proposal", "DEV-000130 worker/recovery contracts"], "may_change": ["Work graph typed contracts and pure domain harness"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["Typed WorkGraph value only; no persistence in this harness"],
|
||||
"state_transitions": ["DRAFT/ADMITTED/RUNNING graph with dependency-aware READY nodes"],
|
||||
"side_effects_and_privileges": ["Pure deterministic validation; no dispatch or external mutation"],
|
||||
"persistence_and_migrations": ["Persistence/scheduler implementation follows after harness"],
|
||||
"failure_retry_restart": ["Cycles, duplicate IDs/keys and invalid budgets reject before scheduling"],
|
||||
"assumptions": ["Graph scheduler must not bypass Agent Execution leases or verifier policy"],
|
||||
"acceptance_scenarios": ["Cycle rejected", "Ready nodes deterministic", "Dependent node remains blocked until prerequisite succeeds", "Budget is validated"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000132",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "b2f922c",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add deterministic Work Graph admission, bounded ready-node scheduling and explicit conflict resolution to the graph harness.",
|
||||
"requirement_ids": ["WP-400", "Graph admission", "Bounded scheduler", "Conflict resolution"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["modules/work/domain/graph_scheduler.go", "tests/agent_execution/graph_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000132-work-graph-admission-scheduler.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "adapters/**", "cmd/**", "runtime/**", ".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-000131 graph harness", "ADR-0021 graph lifecycle"], "may_change": ["Pure graph admission/scheduling/conflict domain functions"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["No persistence; graph values remain deterministic harness state"],
|
||||
"state_transitions": ["DRAFT -> ADMITTED; READY nodes bounded by MaxParallel; conflict unresolved -> explicitly resolved"],
|
||||
"side_effects_and_privileges": ["Pure domain logic only"],
|
||||
"persistence_and_migrations": ["Durable scheduler follows after the harness"],
|
||||
"failure_retry_restart": ["Budget overflow and unknown conflict candidate reject without mutation"],
|
||||
"assumptions": ["Runtime scheduler must use Agent Execution queue/leases in a later task"],
|
||||
"acceptance_scenarios": ["Admission validates attempt budget", "Scheduler respects active/max parallel", "Conflict cannot resolve without explicit known candidate"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000133",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "4a800df",
|
||||
"risk_tier": "E",
|
||||
"intent": "Persist immutable Work Graph revisions with typed nodes and dependency edges for durable scheduler integration.",
|
||||
"requirement_ids": ["WP-401", "Durable graph revision", "Graph node dependencies"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "work-storage"], "paths": ["database/migrations/031_work_graph_persistence.sql", "adapters/postgres/work_repository.go", "tests/agent_execution/graph_persistence_sql_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000133-work-graph-persistence.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "cmd/**", "runtime/**", ".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-000131/132 graph contracts and scheduler"], "may_change": ["Work graph persistence schema and repository methods"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["work.graphs", "work.graph_nodes", "work.graph_edges"],
|
||||
"state_transitions": ["graph revision persisted once; nodes and edges immutable within revision"],
|
||||
"side_effects_and_privileges": ["Work schema only; no cross-owner SQL"],
|
||||
"persistence_and_migrations": ["Append-only migration 031; production apply is later canary"],
|
||||
"failure_retry_restart": ["Idempotent inserts; load reconstructs deterministic node/edge order"],
|
||||
"assumptions": ["Durable scheduler and result/conflict persistence follow"],
|
||||
"acceptance_scenarios": ["Graph revision cannot collide into mutation", "Nodes/edges remain tied to revision", "Load reconstructs graph"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "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-000134",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "e9bb42c",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement the first Truth runtime compiler that produces typed Observation, Claim, Belief and Citation values from an exact source chain.",
|
||||
"requirement_ids": ["WP-100", "Observation/Claim/Belief", "Truth citation provenance"],
|
||||
"allowed_change_scope": {"modules": ["epistemics"], "paths": ["contracts/epistemics/truth.go", "modules/epistemics/domain/truth.go", "tests/research_contracts/truth_bundle_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000134-truth-bundle-compiler.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "adapters/**", "cmd/**", "runtime/**", ".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 contracts", "DEV-000110 citation resolver"], "may_change": ["Typed Truth bundle contracts and pure compiler"]},
|
||||
"semantic_owner": "epistemics",
|
||||
"canonical_state": ["Typed TruthBundle value only; persistence follows"],
|
||||
"state_transitions": ["source chain -> observed observation -> unverified claim -> provisional/conflicted belief"],
|
||||
"side_effects_and_privileges": ["Pure compiler; no fetching, external mutation or authority grant"],
|
||||
"persistence_and_migrations": ["Truth persistence is a later task"],
|
||||
"failure_retry_restart": ["Broken source chain rejects; conflict remains visible"],
|
||||
"assumptions": ["Research executor will supply exact source snapshots/excerpts"],
|
||||
"acceptance_scenarios": ["Observation/claim/citation refs align", "Conflict produces CONFLICTED belief", "Broken source chain is rejected"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000135",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "32e33e3",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add durable lease, stale recovery, and one-shot governed closure for reflection candidates.",
|
||||
"requirement_ids": ["WP-501", "Reflection candidate worker", "Stale claim recovery"],
|
||||
"allowed_change_scope": {"modules": ["epistemics", "epistemics-storage"], "paths": ["contracts/epistemics/types.go", "database/migrations/032_reflection_candidate_leases.sql", "adapters/postgres/epistemics_repository.go", "cmd/reflection-worker/main.go", "tests/research_contracts/reflection_worker_sql_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000135-reflection-worker.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "contracts/authority/**", "modules/cognitive/**", ".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": 1},
|
||||
"contracts": {"consume": ["Existing epistemics ReflectionCandidate contract"], "may_change": ["ReflectionCandidate lease metadata"]},
|
||||
"semantic_owner": "epistemics",
|
||||
"canonical_state": ["epistemics.reflection_candidates"],
|
||||
"state_transitions": ["OPEN to EVALUATING under lease; EVALUATING to OPEN on expiry; EVALUATING to RESOLVED by owning worker"],
|
||||
"side_effects_and_privileges": ["Epistemics schema only; no cognitive call and no proposal promotion"],
|
||||
"persistence_and_migrations": ["Append-only migration 032; production apply is later canary"],
|
||||
"failure_retry_restart": ["FOR UPDATE SKIP LOCKED claim; expired claims are recoverable; one-shot worker is restart-safe"],
|
||||
"assumptions": ["Cognitive evaluation and skill admission remain separate governed stages"],
|
||||
"acceptance_scenarios": ["Concurrent workers claim distinct candidates", "expired evaluation returns to OPEN", "only claim owner can resolve"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "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-000136",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "f73c699",
|
||||
"risk_tier": "E",
|
||||
"intent": "Make model fleet tiers, readiness, and explicit lifeboat fallback durable as typed capability policy.",
|
||||
"requirement_ids": ["WP-601", "Model fleet policy", "Fallback readiness"],
|
||||
"allowed_change_scope": {"modules": ["capability"], "paths": ["contracts/capability/fleet.go", "modules/capability/domain/fleet.go", "modules/capability/domain/fleet_test.go", "tests/cognitive_registry/fleet_contract_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000136-fleet-readiness-policy.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "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 cognitive registry and health observations"], "may_change": ["Typed fleet readiness policy"]},
|
||||
"semantic_owner": "capability",
|
||||
"canonical_state": ["FleetReadiness value object"],
|
||||
"state_transitions": ["observed member readiness determines selected preferred or fallback tier"],
|
||||
"side_effects_and_privileges": ["No provider invocation or external mutation"],
|
||||
"persistence_and_migrations": ["In-memory typed decision; runtime persistence follows fleet registration integration"],
|
||||
"failure_retry_restart": ["No ready fallback yields explicit error"],
|
||||
"assumptions": ["Existing Cognitive Gateway remains the only invocation boundary"],
|
||||
"acceptance_scenarios": ["heavy timeout selects ready lifeboat", "no ready member fails closed"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000137",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "d38406f",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add typed governed self-development gate requiring evidence, task packet, isolated worktree, independent verifier, canary, promotion or rollback.",
|
||||
"requirement_ids": ["WP-701", "Governed self-development", "Independent verification", "Rollback"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution"], "paths": ["contracts/work/self_development.go", "modules/work/domain/self_development.go", "modules/work/domain/self_development_test.go", "tests/semantic_evolution/self_development_contract_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000137-governed-self-development-gate.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "cmd/**", "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": 0},
|
||||
"contracts": {"consume": ["Existing AgentRun and work graph contracts"], "may_change": ["Typed self-development stage gate"]},
|
||||
"semantic_owner": "work-agent-execution",
|
||||
"canonical_state": ["DevelopmentGate value object"],
|
||||
"state_transitions": ["evidence to proposal to task to isolation to independent verification to canary to promotion; canary may rollback"],
|
||||
"side_effects_and_privileges": ["No direct code mutation, provider invocation, or promotion side effect"],
|
||||
"persistence_and_migrations": ["Value object only; durable gate persistence follows execution-plane integration"],
|
||||
"failure_retry_restart": ["Invalid stage transitions and missing evidence fail closed"],
|
||||
"assumptions": ["Task packet and worktree identity are provided by the execution plane"],
|
||||
"acceptance_scenarios": ["same producer cannot verify output", "promotion is terminal", "canary can rollback"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000138",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "1ccd43c",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add executable contract coverage for authenticated cabinet, operations, learning, accessibility, responsive, and reduced-motion surfaces.",
|
||||
"requirement_ids": ["WP-801", "WP-802", "WP-805", "Authenticated cabinet acceptance"],
|
||||
"allowed_change_scope": {"modules": ["creator-web"], "paths": ["tests/creator_web/cabinet_acceptance_contract_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000138-ui-operations-acceptance-contract.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "database/**", "cmd/**", "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 creator-web authenticated API and static cabinet"], "may_change": ["Acceptance tests only"]},
|
||||
"semantic_owner": "creator-web",
|
||||
"canonical_state": ["None; tests inspect existing UI contract"],
|
||||
"state_transitions": ["login session gates cabinet; operations and learning request authenticated API data"],
|
||||
"side_effects_and_privileges": ["No external mutation; live authenticated browser acceptance remains a separate production verification"],
|
||||
"persistence_and_migrations": ["None"],
|
||||
"failure_retry_restart": ["Missing auth, learning, or responsive contract fails CI"],
|
||||
"assumptions": ["Credentials are never embedded in acceptance tests"],
|
||||
"acceptance_scenarios": ["cabinet exposes operations and reflection surfaces", "session and learning routes are authenticated", "mobile/reduced motion rules are present"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_AUTHENTICATED_BROWSER_SESSION", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000139",
|
||||
"task_revision": 2,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "6f5f7e1",
|
||||
"risk_tier": "E",
|
||||
"intent": "Run agent and reflection workers as restartable production processes with DB-owned idle wakeups and protected credential refs.",
|
||||
"requirement_ids": ["WP-302", "WP-502", "Automatic worker runtime", "Production recovery"],
|
||||
"allowed_change_scope": {"modules": ["work-agent-execution", "epistemics", "deployment", "creator-web-acceptance"], "paths": ["contracts/secrets/types.go", "modules/work/application/worker.go", "adapters/postgres/work_repository.go", "cmd/agent-worker/main.go", "adapters/postgres/epistemics_repository.go", "cmd/reflection-worker/main.go", "deploy/docker-compose.prod.yml", "tests/creator_web/verify-remote.ps1", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000139-production-worker-runtime.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "contracts/authority/**", ".gitea/**"]},
|
||||
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 2, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
|
||||
"contracts": {"consume": ["Existing durable dispatch/reflection tables and file secret resolver"], "may_change": ["Worker process lifecycle and production service wiring"]},
|
||||
"semantic_owner": "work-agent-execution and epistemics",
|
||||
"canonical_state": ["work.agent_dispatch_queue", "epistemics.reflection_candidates"],
|
||||
"state_transitions": ["workers recover stale claims, claim durable work, process one cycle, and repeat after DB-owned wakeup"],
|
||||
"side_effects_and_privileges": ["Work and epistemics database roles only; credentials remain CredentialRef/file-backed"],
|
||||
"persistence_and_migrations": ["Uses migrations 030 and 032 already in main"],
|
||||
"failure_retry_restart": ["restartable containers; stale claims recover after restart; -once remains diagnostic mode"],
|
||||
"assumptions": ["Production secret volume contains work and epistemics refs at declared paths"],
|
||||
"acceptance_scenarios": ["both workers stay running idle", "worker survives empty queue", "worker uses protected secret files", "stale dispatch/reflection claims recover"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "docker compose config"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SECRET_REF_MISMATCH", "BLOCKED_BY_MIGRATION_DECISION", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000140",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0018",
|
||||
"base_repo_revision": "07a8de9",
|
||||
"risk_tier": "E",
|
||||
"intent": "Evaluate read-only research evidence into explicit supported, stale, conflicted or unknown findings without persistence or authority mutation.",
|
||||
"requirement_ids": ["WP-102", "research evaluation", "citation provenance", "freshness", "conflict visibility"],
|
||||
"allowed_change_scope": {"modules": ["epistemics-research-runtime"], "paths": ["contracts/epistemics/research.go", "modules/epistemics/domain/research.go", "modules/epistemics/application/research.go", "tests/research_runtime/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000140-research-evaluation-runtime.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "deploy/**", "security/**", "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": ["ADR-0019 typed source, snapshot, excerpt and citation contracts"], "may_change": ["typed research finding and read-only connector boundary"]},
|
||||
"semantic_owner": "epistemics",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["connector evidence -> supported/stale/conflicted/unknown result", "invalid source chain -> rejected"],
|
||||
"side_effects_and_privileges": ["read-only connector boundary; no external mutation; no claim persistence"],
|
||||
"persistence_and_migrations": ["none"],
|
||||
"failure_retry_restart": ["evaluation is pure and repeatable; connector failures remain errors"],
|
||||
"assumptions": ["connectors provide immutable exact excerpts and source snapshots", "proposition keys are connector-supplied typed grouping keys"],
|
||||
"acceptance_scenarios": ["no evidence remains UNKNOWN", "stale evidence is marked STALE", "incompatible statements remain CONFLICTED", "invalid citation chain is rejected", "connector boundary cannot mutate canonical state"],
|
||||
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "research runtime tests"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_ARCHITECTURE_GAP", "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,60 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
var ErrInvalidFleetReadiness = errors.New("invalid fleet readiness")
|
||||
|
||||
func BuildFleetReadiness(observedAt int64, members []capability.FleetMember) (capability.FleetReadiness, error) {
|
||||
if observedAt < 1 || len(members) == 0 {
|
||||
return capability.FleetReadiness{}, ErrInvalidFleetReadiness
|
||||
}
|
||||
result := capability.FleetReadiness{ObservedAtUnixMillis: observedAt, Members: append([]capability.FleetMember(nil), members...), FallbackTier: capability.FleetTierLifeboat}
|
||||
seen := map[capability.FleetTier]bool{}
|
||||
for i := range result.Members {
|
||||
member := &result.Members[i]
|
||||
if member.ProviderInstanceID == "" || member.ModelDescriptorID == "" || !validFleetTier(member.Tier) || seen[member.Tier] && member.Ready {
|
||||
return capability.FleetReadiness{}, ErrInvalidFleetReadiness
|
||||
}
|
||||
if member.Ready {
|
||||
seen[member.Tier] = true
|
||||
}
|
||||
}
|
||||
for _, tier := range []capability.FleetTier{capability.FleetTierFast, capability.FleetTierMedium, capability.FleetTierHeavy, capability.FleetTierLifeboat} {
|
||||
if seen[tier] {
|
||||
result.ReadyTiers = append(result.ReadyTiers, tier)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func SelectFleetTier(readiness capability.FleetReadiness, preferred capability.FleetTier) (capability.FleetTier, error) {
|
||||
if readiness.ObservedAtUnixMillis < 1 || len(readiness.Members) == 0 || !validFleetTier(readiness.FallbackTier) {
|
||||
return "", ErrInvalidFleetReadiness
|
||||
}
|
||||
if slices.Contains(readiness.ReadyTiers, preferred) {
|
||||
return preferred, nil
|
||||
}
|
||||
if slices.Contains(readiness.ReadyTiers, readiness.FallbackTier) {
|
||||
return readiness.FallbackTier, nil
|
||||
}
|
||||
return "", ErrInvalidFleetReadiness
|
||||
}
|
||||
|
||||
func validFleetTier(value capability.FleetTier) bool {
|
||||
return value == capability.FleetTierFast || value == capability.FleetTierMedium || value == capability.FleetTierHeavy || value == capability.FleetTierLifeboat
|
||||
}
|
||||
|
||||
func SortFleetMembers(members []capability.FleetMember) {
|
||||
sort.SliceStable(members, func(i, j int) bool {
|
||||
if members[i].Tier != members[j].Tier {
|
||||
return members[i].Tier < members[j].Tier
|
||||
}
|
||||
return members[i].ProviderInstanceID < members[j].ProviderInstanceID
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func TestFleetReadinessFallsBackToLifeboatWhenPrimaryTiersUnavailable(t *testing.T) {
|
||||
readiness, err := BuildFleetReadiness(100, []capability.FleetMember{{ProviderInstanceID: "cpu", ModelDescriptorID: "qwen", Tier: capability.FleetTierLifeboat, Ready: true, Reason: "local"}, {ProviderInstanceID: "gpu", ModelDescriptorID: "heavy", Tier: capability.FleetTierHeavy, Ready: false, Reason: "timeout"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
selected, err := SelectFleetTier(readiness, capability.FleetTierHeavy)
|
||||
if err != nil || selected != capability.FleetTierLifeboat {
|
||||
t.Fatalf("selected=%q err=%v", selected, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
domain "git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||
)
|
||||
|
||||
// ReadOnlyConnector is the only integration point for web/repository sources.
|
||||
// Implementations return immutable evidence; they cannot persist claims.
|
||||
type ReadOnlyConnector interface {
|
||||
Collect(context.Context, epistemics.ResearchPlan) ([]epistemics.ResearchCandidate, error)
|
||||
}
|
||||
|
||||
type ResearchService struct{ Connector ReadOnlyConnector }
|
||||
|
||||
func (s ResearchService) Evaluate(ctx context.Context, plan epistemics.ResearchPlan, nowUnixMillis int64) (epistemics.ResearchResult, error) {
|
||||
if s.Connector == nil {
|
||||
return epistemics.ResearchResult{}, domain.ErrNoResearchEvidence
|
||||
}
|
||||
candidates, err := s.Connector.Collect(ctx, plan)
|
||||
if err != nil {
|
||||
return epistemics.ResearchResult{}, err
|
||||
}
|
||||
return domain.EvaluateResearch(plan, candidates, nowUnixMillis)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidResearchPlan = errors.New("invalid research plan")
|
||||
ErrInvalidResearchCandidate = errors.New("invalid research candidate")
|
||||
ErrNoResearchEvidence = errors.New("no research evidence")
|
||||
)
|
||||
|
||||
// EvaluateResearch turns connector evidence into an explainable, non-canonical
|
||||
// result. It is deliberately pure: it does not call a provider, persist a
|
||||
// claim, or promote an assessment into authority.
|
||||
func EvaluateResearch(plan epistemics.ResearchPlan, candidates []epistemics.ResearchCandidate, evaluatedAtUnixMillis int64) (epistemics.ResearchResult, error) {
|
||||
if err := plan.Validate(); err != nil || evaluatedAtUnixMillis < 1 {
|
||||
return epistemics.ResearchResult{}, ErrInvalidResearchPlan
|
||||
}
|
||||
result := epistemics.ResearchResult{PlanID: plan.ID, EvaluatedAtUnixMillis: evaluatedAtUnixMillis}
|
||||
if len(candidates) == 0 {
|
||||
result.Findings = []epistemics.ResearchFinding{{State: epistemics.FindingUnknown, Reason: "no connector evidence was returned"}}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
groups := make(map[string][]epistemics.ResearchCandidate)
|
||||
order := make([]string, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
if strings.TrimSpace(candidate.PropositionKey) == "" || strings.TrimSpace(candidate.Statement) == "" || candidate.Evidence == "" {
|
||||
return epistemics.ResearchResult{}, ErrInvalidResearchCandidate
|
||||
}
|
||||
if _, exists := groups[candidate.PropositionKey]; !exists {
|
||||
order = append(order, candidate.PropositionKey)
|
||||
}
|
||||
groups[candidate.PropositionKey] = append(groups[candidate.PropositionKey], candidate)
|
||||
}
|
||||
|
||||
for _, key := range order {
|
||||
finding, err := evaluateGroup(key, groups[key], evaluatedAtUnixMillis)
|
||||
if err != nil {
|
||||
return epistemics.ResearchResult{}, err
|
||||
}
|
||||
result.Findings = append(result.Findings, finding)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func evaluateGroup(key string, candidates []epistemics.ResearchCandidate, evaluatedAt int64) (epistemics.ResearchFinding, error) {
|
||||
if len(candidates) == 0 {
|
||||
return epistemics.ResearchFinding{}, ErrNoResearchEvidence
|
||||
}
|
||||
first := candidates[0]
|
||||
firstBundle, err := compileResearchCandidate(first)
|
||||
if err != nil {
|
||||
return epistemics.ResearchFinding{}, ErrInvalidResearchCandidate
|
||||
}
|
||||
finding := epistemics.ResearchFinding{PropositionKey: key, Statement: first.Statement, Claim: firstBundle.Claim, Citations: []epistemics.Citation{firstBundle.Citation}, State: epistemics.FindingSupported}
|
||||
statements := map[string]struct{}{normalize(first.Statement): {}}
|
||||
if first.Snapshot.Freshness == epistemics.FreshnessUnknown || first.Evidence == epistemics.EvidenceAssumed || first.Coverage != epistemics.CoverageComplete {
|
||||
finding.State, finding.Reason = epistemics.FindingUnknown, "evidence is assumed, unknown or incomplete"
|
||||
} else if first.Snapshot.Freshness == epistemics.FreshnessStale {
|
||||
finding.State, finding.Reason = epistemics.FindingStale, "source snapshot is stale"
|
||||
}
|
||||
for _, candidate := range candidates[1:] {
|
||||
bundle, resolveErr := compileResearchCandidate(candidate)
|
||||
if resolveErr != nil {
|
||||
return epistemics.ResearchFinding{}, ErrInvalidResearchCandidate
|
||||
}
|
||||
finding.Citations = append(finding.Citations, bundle.Citation)
|
||||
statements[normalize(candidate.Statement)] = struct{}{}
|
||||
if candidate.Snapshot.Freshness == epistemics.FreshnessUnknown || candidate.Evidence == epistemics.EvidenceAssumed || candidate.Coverage != epistemics.CoverageComplete {
|
||||
finding.State, finding.Reason = epistemics.FindingUnknown, "at least one source has assumed, unknown or incomplete evidence"
|
||||
} else if candidate.Snapshot.Freshness == epistemics.FreshnessStale {
|
||||
finding.State, finding.Reason = epistemics.FindingStale, "at least one source snapshot is stale"
|
||||
}
|
||||
}
|
||||
if len(statements) > 1 {
|
||||
finding.State, finding.Reason = epistemics.FindingConflicted, "connector evidence contains incompatible statements"
|
||||
}
|
||||
for _, citation := range finding.Citations {
|
||||
if citation.Conflict == epistemics.ConflictPresent {
|
||||
finding.State, finding.Reason = epistemics.FindingConflicted, "connector marked evidence as conflicting"
|
||||
}
|
||||
}
|
||||
if len(finding.Citations) == 0 || evaluatedAt < 1 {
|
||||
return epistemics.ResearchFinding{}, ErrInvalidResearchCandidate
|
||||
}
|
||||
return finding, nil
|
||||
}
|
||||
|
||||
func normalize(value string) string { return strings.ToLower(strings.Join(strings.Fields(value), " ")) }
|
||||
|
||||
// candidateConflict keeps unknown source freshness explicit at the candidate
|
||||
// boundary. A connector may mark evidence as conflicting without changing the
|
||||
// source; group-level incompatible statements are detected separately.
|
||||
func compileResearchCandidate(candidate epistemics.ResearchCandidate) (epistemics.TruthBundle, error) {
|
||||
if candidate.Coverage == "" || candidate.Conflict == "" {
|
||||
return epistemics.TruthBundle{}, ErrInvalidResearchCandidate
|
||||
}
|
||||
citation, err := epistemics.ResolveCitation(candidate.Source, candidate.Snapshot, candidate.Excerpt, candidate.Evidence, candidate.Coverage, candidate.Conflict, candidate.Snapshot.CapturedAtUnixMillis)
|
||||
if err != nil {
|
||||
return epistemics.TruthBundle{}, err
|
||||
}
|
||||
observation := epistemics.Observation{ID: epistemics.ObservationID("observation:" + string(candidate.Excerpt.ID)), SourceSnapshot: candidate.Snapshot.ID, Excerpt: candidate.Excerpt.ID, Text: candidate.Excerpt.Text, ObservedAt: candidate.Snapshot.CapturedAtUnixMillis}
|
||||
claim := epistemics.Claim{ID: epistemics.ClaimID("claim:" + string(candidate.Excerpt.ID)), Statement: candidate.Statement, ObservationID: observation.ID, CitationID: citation.ID, Verification: epistemics.VerificationUnverified}
|
||||
return epistemics.TruthBundle{Observation: observation, Claim: claim, Belief: epistemics.Belief{ID: epistemics.BeliefID("belief:" + string(candidate.Excerpt.ID)), ClaimID: claim.ID, State: epistemics.BeliefProvisional}, Citation: citation}, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
)
|
||||
|
||||
var ErrInvalidTruthBundle = errors.New("invalid truth bundle")
|
||||
|
||||
func CompileObservedClaim(source epistemics.Source, snapshot epistemics.SourceSnapshot, excerpt epistemics.SourceExcerpt, statement string, observedAt int64, conflict epistemics.ConflictState) (epistemics.TruthBundle, error) {
|
||||
if strings.TrimSpace(statement) == "" || observedAt < 1 {
|
||||
return epistemics.TruthBundle{}, ErrInvalidTruthBundle
|
||||
}
|
||||
citation, err := epistemics.ResolveCitation(source, snapshot, excerpt, epistemics.EvidenceObserved, epistemics.CoverageComplete, conflict, observedAt)
|
||||
if err != nil {
|
||||
return epistemics.TruthBundle{}, ErrInvalidTruthBundle
|
||||
}
|
||||
observation := epistemics.Observation{ID: epistemics.ObservationID("observation:" + string(excerpt.ID)), SourceSnapshot: snapshot.ID, Excerpt: excerpt.ID, Text: excerpt.Text, ObservedAt: observedAt}
|
||||
claim := epistemics.Claim{ID: epistemics.ClaimID("claim:" + string(excerpt.ID)), Statement: statement, ObservationID: observation.ID, CitationID: citation.ID, Verification: epistemics.VerificationUnverified}
|
||||
state := epistemics.BeliefProvisional
|
||||
if conflict == epistemics.ConflictPresent || conflict == epistemics.ConflictUnknown {
|
||||
state = epistemics.BeliefConflicted
|
||||
}
|
||||
bundle := epistemics.TruthBundle{Observation: observation, Claim: claim, Belief: epistemics.Belief{ID: epistemics.BeliefID("belief:" + string(excerpt.ID)), ClaimID: claim.ID, State: state}, Citation: citation}
|
||||
if bundle.Observation.SourceSnapshot != snapshot.ID || bundle.Claim.ObservationID != bundle.Observation.ID || bundle.Claim.CitationID != bundle.Citation.ID || bundle.Belief.ClaimID != bundle.Claim.ID {
|
||||
return epistemics.TruthBundle{}, ErrInvalidTruthBundle
|
||||
}
|
||||
return bundle, nil
|
||||
}
|
||||
@@ -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,43 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/ports"
|
||||
)
|
||||
|
||||
type WorkerCycleResult struct {
|
||||
RecoveredClaims int64
|
||||
DispatchID string
|
||||
Completed bool
|
||||
Idle bool
|
||||
}
|
||||
|
||||
func ExecuteWorkerCycle(ctx context.Context, store ports.WorkerStore, workerID string, token work.AgentFencingToken, now time.Time, staleAfter, leaseTTL time.Duration) (WorkerCycleResult, error) {
|
||||
recovered, err := store.RecoverStaleClaims(ctx, now.UnixMilli(), staleAfter.Milliseconds())
|
||||
if err != nil {
|
||||
return WorkerCycleResult{}, err
|
||||
}
|
||||
item, err := store.ClaimNextDispatch(ctx, workerID, now.UnixMilli())
|
||||
if err != nil {
|
||||
return WorkerCycleResult{RecoveredClaims: recovered}, err
|
||||
}
|
||||
if item.DispatchID == "" {
|
||||
return WorkerCycleResult{RecoveredClaims: recovered, Idle: true}, nil
|
||||
}
|
||||
intent, err := store.LoadDispatchIntent(ctx, item.DispatchID)
|
||||
if err != nil {
|
||||
_ = store.CompleteDispatch(ctx, item.DispatchID, workerID, work.AgentDispatchFailed, now.UnixMilli())
|
||||
return WorkerCycleResult{RecoveredClaims: recovered, DispatchID: item.DispatchID}, err
|
||||
}
|
||||
if _, err := ExecuteDryRunDispatch(ctx, store, intent, workerID, token, now, leaseTTL); err != nil {
|
||||
_ = store.CompleteDispatch(ctx, item.DispatchID, workerID, work.AgentDispatchFailed, now.UnixMilli())
|
||||
return WorkerCycleResult{RecoveredClaims: recovered, DispatchID: item.DispatchID}, err
|
||||
}
|
||||
if err := store.CompleteDispatch(ctx, item.DispatchID, workerID, work.AgentDispatchCompleted, now.UnixMilli()); err != nil {
|
||||
return WorkerCycleResult{RecoveredClaims: recovered, DispatchID: item.DispatchID}, err
|
||||
}
|
||||
return WorkerCycleResult{RecoveredClaims: recovered, DispatchID: item.DispatchID, Completed: true}, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user