Record every harness run in ai_runs with redaction and purge
CI / ci (push) Successful in 1m35s
CI / ci (pull_request) Successful in 1m32s

harness.Run now writes one ai_runs row per run that reaches the provider,
covering the repair turn: skill, version, provider, model, temperature,
status (ok, repaired, bad_output, provider_error), latency, summed token
counts when reported, error text and the per-turn request and response
bodies. Every stored string is redacted first: the decrypted API key,
Authorization and API-key header values, Google Chat webhook URLs and
URLs with key-like query or fragment parameters or user info. JSON is
decoded and its strings redacted, so escapes cannot hide a credential.
A row that cannot be written fails the run.

Purge deletes rows created before a cutoff (RunRetention is 30 days),
clearing suggestion_batches and week_plans references in the same
transaction.

Migration 0006 rebuilds ai_runs with the new status CHECK and a
created_at index; a pre-harness running or error row stops it.

Maestro task 53e382d8-1db3-4555-a31d-e44cf55cc98d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 09:32:20 -04:00
co-authored by Claude Opus 5.5
parent 5d5404b3cb
commit b0cf6fb951
13 changed files with 1141 additions and 37 deletions
+107
View File
@@ -0,0 +1,107 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: ai_runs.sql
package db
import (
"context"
"database/sql"
)
const clearSuggestionBatchAIRunsBefore = `-- name: ClearSuggestionBatchAIRunsBefore :execrows
UPDATE suggestion_batches
SET ai_run_id = NULL
WHERE ai_run_id IN (SELECT ai_runs.id FROM ai_runs WHERE ai_runs.created_at < ?1)
`
// Drops references to the runs created before the cutoff, so the purge
// can delete them.
func (q *Queries) ClearSuggestionBatchAIRunsBefore(ctx context.Context, cutoff string) (int64, error) {
result, err := q.db.ExecContext(ctx, clearSuggestionBatchAIRunsBefore, cutoff)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const clearWeekPlanAIRunsBefore = `-- name: ClearWeekPlanAIRunsBefore :execrows
UPDATE week_plans
SET ai_run_id = NULL
WHERE ai_run_id IN (SELECT ai_runs.id FROM ai_runs WHERE ai_runs.created_at < ?1)
`
// Drops references to the runs created before the cutoff, so the purge
// can delete them.
func (q *Queries) ClearWeekPlanAIRunsBefore(ctx context.Context, cutoff string) (int64, error) {
result, err := q.db.ExecContext(ctx, clearWeekPlanAIRunsBefore, cutoff)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const createAIRun = `-- name: CreateAIRun :exec
INSERT INTO ai_runs (
id, skill, skill_version, provider, model, temperature, status,
latency_ms, tokens_in, tokens_out, error, request_redacted,
response_redacted, created_at, updated_at
) VALUES (
?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?
)
`
type CreateAIRunParams struct {
ID string
Skill string
SkillVersion string
Provider string
Model string
Temperature float64
Status string
LatencyMs sql.NullInt64
TokensIn sql.NullInt64
TokensOut sql.NullInt64
Error sql.NullString
RequestRedacted string
ResponseRedacted sql.NullString
CreatedAt string
UpdatedAt string
}
func (q *Queries) CreateAIRun(ctx context.Context, arg CreateAIRunParams) error {
_, err := q.db.ExecContext(ctx, createAIRun,
arg.ID,
arg.Skill,
arg.SkillVersion,
arg.Provider,
arg.Model,
arg.Temperature,
arg.Status,
arg.LatencyMs,
arg.TokensIn,
arg.TokensOut,
arg.Error,
arg.RequestRedacted,
arg.ResponseRedacted,
arg.CreatedAt,
arg.UpdatedAt,
)
return err
}
const deleteAIRunsBefore = `-- name: DeleteAIRunsBefore :execrows
DELETE FROM ai_runs
WHERE created_at < ?1
`
func (q *Queries) DeleteAIRunsBefore(ctx context.Context, cutoff string) (int64, error) {
result, err := q.db.ExecContext(ctx, deleteAIRunsBefore, cutoff)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
+8
View File
@@ -15,11 +15,19 @@ type Querier interface {
// Marks the oldest queued job running and returns it. Returns sql.ErrNoRows
// when no job is queued.
ClaimNextJob(ctx context.Context, now sql.NullString) (Job, error)
// Drops references to the runs created before the cutoff, so the purge
// can delete them.
ClearSuggestionBatchAIRunsBefore(ctx context.Context, cutoff string) (int64, error)
// Drops references to the runs created before the cutoff, so the purge
// can delete them.
ClearWeekPlanAIRunsBefore(ctx context.Context, cutoff string) (int64, error)
CreateAIRun(ctx context.Context, arg CreateAIRunParams) error
CreateDinner(ctx context.Context, arg CreateDinnerParams) (Dinner, error)
CreateJob(ctx context.Context, arg CreateJobParams) error
CreatePhoto(ctx context.Context, arg CreatePhotoParams) (Photo, error)
CreateRecipeIngredient(ctx context.Context, arg CreateRecipeIngredientParams) error
CreateRecipeStep(ctx context.Context, arg CreateRecipeStepParams) error
DeleteAIRunsBefore(ctx context.Context, cutoff string) (int64, error)
DeleteRecipeIngredients(ctx context.Context, recipeID string) error
DeleteRecipeSteps(ctx context.Context, recipeID string) error
FailJob(ctx context.Context, arg FailJobParams) (int64, error)