Skip to content

UX audit fixes: accurate is_new on run tools, approver reason capture, human-readable approval pages - #5

Merged
usehoplite[bot] merged 2 commits into
mainfrom
hoplite/ialysos-f0d14473
Sep 2, 2026
Merged

UX audit fixes: accurate is_new on run tools, approver reason capture, human-readable approval pages#5
usehoplite[bot] merged 2 commits into
mainfrom
hoplite/ialysos-f0d14473

Conversation

@usehoplite

@usehoplite usehoplite Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Audit of the main user flows (workflow run/start tools, approval gates + web UI, webhooks, deadline tools) surfaced three verifiable papercuts, all fixed here:

  1. workflow.run / intake.run always reported is_new: true — both tools documented is_new as "False if idempotent return", but the value was hardcoded (is_new=True # simplified) or a tautology (run.trigger.dedupe_key == idempotency_key). An agent resubmitting a duplicate lead could never tell the run was deduped. start_run now accepts an optional caller-supplied run id; comparing it to the returned run's id detects a dedupe hit race-free (the store's create_run dedupe check remains the single source of truth).

  2. The approver's reason was silently dropped. approval.decide advertised an optional reason, and ApprovalDecision.reason exists in the model and is persisted by both stores — but resolve_gate never accepted one. The reason is now threaded through to the decision record and the hash-chained audit stream (gate.approved / gate.rejected payloads).

  3. The approval web UI returned raw JSON to humans. An attorney clicking an approval link from email saw {"detail": "Token has expired."} on error and a JSON blob after clicking Approve/Reject. The pages now:

    • show the workflow being approved (fail-soft store lookup) alongside run/step/expiry,
    • offer an optional reason textarea (recorded with the decision),
    • render a readable "Decision recorded" confirmation,
    • render readable error pages for invalid/expired/used tokens and authz denials, with HTTP status codes unchanged.

Known issue found, intentionally not changed here

With no authenticated session, the web/email channels submit actor="web_anonymous", which never satisfies resolve_gate's role check — every web approval is denied 403 today. Fixing that means deciding the authorization model for token-possessed approvers (token-as-credential vs. session auth), which is a security-design decision, not a papercut. Flagged for a follow-up; the new HTML error page at least makes the denial readable.

Verification

  • uv run pytest -q: 549 passed, 1 skipped (baseline 542 + 7 new tests), including Postgres-backed suites run against a live Postgres 16 container.
  • New tests: dedupe-aware is_new for intake.run and workflow.run (E2E-05 extended + new E2E-06), reason recorded on decision + audit stream, reason defaults to None, approval page shows workflow + reason field, HTML 401 on invalid token, confirmation page records reason/channel.
  • uv run ruff check src tests: clean. uv run mypy src/cam (strict): clean, 145 files.

Open in Hoplite

usehoplite Bot added 2 commits September 2, 2026 19:14
- workflow.run / intake.run: report is_new truthfully (was hardcoded True
  despite the documented dedupe contract); start_run accepts an optional
  caller run id so a dedupe hit is detectable race-free.
- approval.decide / web approvals: record the approver's reason (input was
  documented and the decision model had the field, but resolve_gate never
  accepted one); reason now lands in the decision record and audit stream.
- /approvals web UI: render human-readable confirmation and error pages
  instead of raw JSON, show the workflow being approved, and add a reason
  field to the form. Status codes unchanged.
Setup installs git-lfs (required by the PR publication hook) and syncs all
extras; check runs the suite, ruff, and strict mypy.
@usehoplite
usehoplite Bot merged commit 7e33436 into main Sep 2, 2026
5 checks passed
@pt-act
pt-act deleted the hoplite/ialysos-f0d14473 branch September 2, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants