Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .changeset/dashboard-surfaces-verdict-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
---

Dashboard `/dashboard/agents` surfaces the new `verdict_source` field on the
compliance tile and a per-run "Your test / Heartbeat / Manual / Webhook"
badge in the History panel. PR 2 of the #4247 unification stack —
read-side cleanup that lets owners distinguish their own on-demand
runs from scheduled heartbeat verdicts at a glance.

**Context.** PR #4250 added `verdict_source` to
`/api/registry/agents/:url/compliance` and `triggered_by` to each row
returned by `/api/registry/agents/:url/compliance/history`. Both fields
were unrendered in the dashboard until this PR.

**What changes.**

- Compliance tile shows `Last checked: 3m ago (your test)` /
`(heartbeat)` / `(manual)` / `(webhook)` after the timestamp. Empty
string when `verdict_source` is null (never run).
- History panel renders a colored badge per run row:
- `Your test` (info-blue) for `triggered_by = 'owner_test'`
- `Heartbeat` (neutral) for `triggered_by = 'heartbeat'`
- `Manual` / `Webhook` (neutral) for the other enum values

No backend changes; this is pure UI surfacing of fields the API already
emits. Pre-PR-1 rows (which only have `'heartbeat'` / `'manual'` /
`'webhook'`) render with the neutral badge — no regression.

**Out of scope** (PR 3 of #4247): dropping `agent_test_history` and
backfilling owner-triggered rows. Tracked separately so the destructive
migration soaks behind the read-only UI change.
37 changes: 37 additions & 0 deletions .changeset/derive-agent-context-last-test-from-canonical.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
---

PR 4 of the #4247 unification stack. Replaces direct reads of
`agent_contexts.last_test_*` with a view that derives them from
`agent_compliance_runs` — the canonical source PR #4250 unified onto.

**What changes.**

- New column `agent_compliance_runs.triggered_org_id` (nullable). Populated
by the owner-test write path in `evaluate_agent_quality` using the
caller's `organizationId`. Heartbeat / manual / webhook writes leave it
NULL — they don't have an org dimension. Without this column, two orgs
that own the same agent URL (e.g. staging and prod orgs of one publisher)
would conflate their test history through a join on `agent_url` alone.
- New view `agent_context_with_latest_test`: `agent_contexts.*` joined to
the latest non-dry-run `agent_compliance_runs` row scoped by
`(triggered_org_id, agent_url)` via `LEFT JOIN LATERAL`, plus a COUNT
scalar subquery for `total_tests_run`. Surfaces the derived fields as
`canonical_last_test_*` so the column-rename in the SELECT is explicit.
- `AgentContextDatabase.getByOrganization`, `getById`, `getByOrgAndUrl`
now SELECT from the view and alias `canonical_last_test_*` →
`last_test_*` so callers see no shape change.

**Backward compat.** The legacy `agent_contexts.last_test_*` columns stay.
Third-party (non-owner) `recordTest()` writes still update them — that's
the session-scoped audit trail PR 3 of #4247 retained for non-owner runs.
The columns become dead-letter once `agent_test_history` is dropped (gated
on the soak windows in #4247) and `recordTest()` retires in the follow-up
"final cleanup" PR.

**Index.** `idx_agent_compliance_runs_triggered_org_url_at` on
`(triggered_org_id, agent_url, tested_at DESC)` (partial, only where
`triggered_org_id IS NOT NULL`) supports the view's per-org `DISTINCT ON`
lookup as a single index scan.

**Stacked on** #4264 (PR 3) → #4263 (PR 2) → #4250 (PR 1).
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
---

Final cleanup of the #4247 compliance-state unification stack. Drops
`agent_test_history` and the `agent_contexts.last_test_*` columns now
that owner test runs persist canonically via
`agent_compliance_runs.triggered_by='owner_test'` (PR #4250) and
read-side derivation goes through `agent_context_with_latest_test`
(PR #4268).

**Pre-merge gate (load-bearing — destructive migration):**

1. PR #4250 ≥ 14 days live in prod with zero canonical-write incidents.
2. PR #4263 ≥ 7 days live in prod with the dashboard rendering identical
verdicts via the view-derived path.
3. PR #4264's migration 472 has run; row-count delta on staging is ±0
(every owner-triggered `agent_test_history` row backfilled into
`agent_compliance_runs`).
4. Third-party (`user_id IS NULL`) rows from `agent_test_history`
exported to S3 cold storage. Export evidence committed to the ops
runbook before the migration runs. Reversibility path is the export,
not pg_dump.
5. PR #4268's view + reader migration confirmed working in prod.

**What this PR does.**

- **Migration 474.** Redefines `agent_context_summary` view without
references to the dropped table/columns; drops
`agent_contexts.last_test_*` columns; drops `agent_test_history`
table; refreshes `agent_context_with_latest_test` so the view's
`ac.*` projection no longer carries the removed columns.
- **`agent-context-db.ts`.** Removes `recordTest`, `getTestHistory`,
`getLatestTestForUser`, the `AgentTestHistory` interface, and the
`RecordTestInput` interface. The `last_test_*` SET branches in
`update()` go away; the method now refetches via `getById()` after
the UPDATE so derived view fields stay populated.
- **`evaluate_agent_quality`.** The third-party `recordTest()` write
path is removed. Non-owner runs are now session-scoped — they return
results in the response and do not persist.
- **`run_storyboard`.** The `recordTest()` write path is removed.
Single-storyboard runs remain session-scoped (they don't write
canonical state because that would over-state the test coverage of
a single storyboard). A future `triggered_by = 'storyboard_test'`
enum value would expand canonical writes here, but that's a separate
design discussion.

**Behavior change.**

- Third-party / non-owner `evaluate_agent_quality` runs against
someone else's agent no longer leave any persistent state in the
registry. Matches the "owner-only canonical writes" policy from
#4247. Stranger-runs return results to the caller in the same
shape; they just don't persist.
- `run_storyboard` runs (any caller) no longer leave persistent state
in the registry. The dashboard's "tested at" timestamps for an org
reflect only `evaluate_agent_quality` runs (which exercise the full
comply suite); single-storyboard runs are exploratory tooling.

**Stacked on** #4268 (PR 4) → #4264 (PR 3) → #4263 (PR 2) → #4250 (PR 1).
10 changes: 10 additions & 0 deletions .changeset/unify-owner-compliance-writes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
---

PR 1 of 4 in the compliance-state unification initiative (issue #4247): owner-triggered
`evaluate_agent_quality` runs now write to canonical compliance tables
(`agent_compliance_status`, `agent_compliance_runs`, `agent_storyboard_status`) with
`triggered_by = 'owner_test'`, closing the 12-hour gap between owner tests and the
public `/api/registry/agents/:url/compliance` endpoint. Non-owner runs continue
writing to `agent_test_history` (deprecated in PR 3). Adds `'owner_test'` to both
`triggered_by` CHECK constraints via migration 471.
33 changes: 33 additions & 0 deletions .changeset/unify-owner-evaluate-agent-stop-legacy-write.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
---

PR 3 of the #4247 unification stack. Two coupled changes:

**Backfill historical owner-triggered tests into the canonical compliance
tables.** Migration `472_backfill_agent_test_history_to_compliance_runs.sql`
copies every `agent_test_history` row with a `user_id` into
`agent_compliance_runs` as `triggered_by = 'owner_test'` (carrying the
source row id in `observations_json.backfill_source_id` so a re-run is a
no-op via `WHERE NOT EXISTS`). Each agent's most-recent backfilled row
also upserts into `agent_compliance_status` so the dashboard's compliance
tile immediately reflects a real verdict for any agent that was tested
through Addie pre-PR-#4250 and never ran the heartbeat.

**Stop the dual write for owner runs.** `evaluate_agent_quality` no longer
calls `agentContextDb.recordTest()` when the caller owns the agent — that
path was the dual-write bug #4247 is closing. The legacy `recordTest` call
is retained ONLY for third-party runs so a stranger who tests someone
else's agent still has a session-scoped audit trail in their own
`agent_test_history`. Owner-triggered runs persist exclusively to
canonical state going forward.

**Out of scope** (deferred to a follow-up after the soak gates):

- Drop `agent_test_history` table — gated on the 14-day soak from #4250
deploy + 7-day soak from #4263 + S3 cold-storage export of the
remaining (`user_id IS NULL`) third-party rows. Migration 472 documents
this in its trailing comment.
- Collapse `agent_contexts.last_test_*` into a derived view — PR 4 of
the #4247 stack.

**Stacked on** #4263 (PR 2 of #4247) → #4250 (PR 1 of #4247).
27 changes: 27 additions & 0 deletions dist/schemas/onboarding-openapi.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* OpenAPI registrations for the onboarding REST surface.
*
* `POST /api/organizations` has existed in production for a long time but
* has only ever been documented as a private endpoint exercised by the AAO
* dashboard's `/onboarding` form. Surfacing it in the public spec is the
* minimum-surface answer to the storefront-bootstrap question: a
* third-party app holding only a user's OAuth token needs *one* documented
* call to materialize the org, then `POST /api/me/agents` to land an agent
* (which auto-creates the member profile on first call).
*
* Two fields the handler accepts but the public schema deliberately omits:
*
* - `membership_tier` — owned exclusively by the Stripe webhook. Accepting
* it from the caller would let any user stamp tier intent on their org
* row, leaking tier-gated UI state until/unless a real subscription
* overwrites the column.
* - `corporate_domain` — server derives the value from the authenticated
* user's email. Accepting it as a field invited 400s when a caller's
* value disagreed with their email and gave nothing back when it agreed.
*
* Kept in its own module so the spec generator's import graph stays free
* of route handlers (each route file's transitive imports pull in WorkOS
* init, which fails at module load without env vars).
*/
export {};
//# sourceMappingURL=onboarding-openapi.d.ts.map
1 change: 1 addition & 0 deletions dist/schemas/onboarding-openapi.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 135 additions & 0 deletions dist/schemas/onboarding-openapi.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading