Skip to content

chore(harness): enforce read-only audit agents at the tool layer (gate over prose) - #449

Merged
thomasluizon merged 1 commit into
mainfrom
chore/harness-audit-readonly-gate
Jul 10, 2026
Merged

chore(harness): enforce read-only audit agents at the tool layer (gate over prose)#449
thomasluizon merged 1 commit into
mainfrom
chore/harness-audit-readonly-gate

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Why

The /audit-* and /prod-readiness workflows are read-only by contract ("assess, never edit"), but their Haiku finders / critics / skeptics / ops agents were spawned via agent(...) with no agentType — i.e. the default write-capable tool profile. Nothing at the tool layer enforced the contract.

On 2026-07-10 a /prod-readiness run (423 agents) drifted from describing fixes to applying them — prompted with "write the CONCRETE missing test" and "fix (the concrete change)" while holding Write/Edit/Bash, the agents wrote ~20 stray files across both repos: an invented avatar-upload feature, new + edited test files (several non-compiling), and a @next/bundle-analyzer install. Discovered because they polluted a working tree and broke the build. (The stray files are preserved on salvage/audit-spew-243 in each repo for later triage.)

This is a textbook gates-over-prose miss (root CLAUDE.md rule 6): a prose contract is advisory and decays; make it a tool-level gate.

Fix

  • New audit-readonly subagent (.claude/agents/audit-readonly.md, tools: Glob, Grep, Read only — no Write/Edit/Bash) + its .opencode pointer (edit/bash/task/webfetch/websearch denied).
  • audit.mjs: every finder / skeptic / critic / gap-finder agent() now runs as agentType: 'audit-readonly'.
  • prod-readiness.mjs: the ops + verify agents likewise.
  • Prompt softening (defense-in-depth behind the gate): tests-audit now says "SPECIFY the missing test as text in the fix field, never write it to disk".

With the tool gate, prompt drift can no longer touch disk regardless of phrasing.

Notes / verification

  • Both .mjs files parse in the harness execution context; .gitattributes keeps them LF; diff is minimal (5 lines in audit.mjs, 2 in prod-readiness.mjs).
  • Takes effect next session (the agent registry loads at startup), so it can't be smoke-tested in the session that authored it.
  • Follow-up: capture this as a /lesson so it graduates into the harness rules.

Refs #243.

🤖 Generated with Claude Code

…e over prose)

The prod-readiness and audit workflows are read-only by contract ("assess, never
edit"), but their Haiku finders / critics / skeptics / ops agents were spawned with
the default write-capable tool profile. On 2026-07-10 a prod-readiness run drifted
from describing fixes to applying them, writing ~20 stray files across both repos
(an invented feature, broken test edits, a dependency install). The contract lived
only in prose, so nothing stopped it.

Make it a gate (root CLAUDE.md rule 6):
- New audit-readonly subagent (tools: Glob, Grep, Read only; no Write/Edit/Bash),
  plus its .opencode pointer (edit/bash/task/webfetch/websearch denied).
- audit.mjs: every finder / skeptic / critic / gap-finder agent() now runs as
  agentType: 'audit-readonly'.
- prod-readiness.mjs: the ops + verify agents likewise.
- Soften the tests-audit prompt ("SPECIFY the missing test as text in the fix
  field, never write it to disk") as defense-in-depth behind the tool gate.

Takes effect next session (the agent registry loads at startup). Refs #243

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
orbit-ui-mobile-web Ignored Ignored Jul 10, 2026 10:55pm

Request Review

@sonarqubecloud

Copy link
Copy Markdown

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Complete

Scope: PR #449 in thomasluizon/orbit-ui-mobile — chore(harness): enforce read-only audit agents at the tool layer
Recommendation: APPROVE

Severity Count
Critical (incl. old-client breaks) 0
High 0
Medium 2
Low / Info 0

Code Review: PR #449

Scope: PR #449, thomasluizon/orbit-ui-mobile — 4 files changed: .claude/agents/audit-readonly.md (new), .claude/workflows/audit.mjs (5 lines), .claude/workflows/prod-readiness.mjs (2 lines), .opencode/agents/audit-readonly.md (new).

Summary

The PR closes a real incident (2026-07-10: unrestricted Haiku finder/skeptic/critic agents drifted from describing fixes to writing ~20 stray files) by moving the read-only contract from prose into a tool-layer gate. It adds a new audit-readonly subagent restricted to tools: Glob, Grep, Read and wires agentType: 'audit-readonly' into every finder/skeptic/critic/gap-finder call in audit.mjs and every ops/verify call in prod-readiness.mjs, plus the matching .opencode pointer. The new agent files match the established convention exactly (checked against contract-aligner.md/security-reviewer.md and their pointers). No apps/*, packages/shared, or orbit-api files are touched.

Findings

Critical / High

None.

Medium

[Medium] Prompt-softening is inconsistent — the shared base template keeps the exact phrase implicated in the incident
· dimension: 1 Correctness / 5 No-workaround (defense-in-depth layer)
· location: .claude/workflows/audit.mjs:157 (finderPrompt, shared by all four kinds)
· issue: The PR body itself quotes "fix (the concrete change)" as one of the two phrasings that drifted agents from describing to applying fixes. That exact string is untouched in the shared finderPrompt template used by every kind. Only the tests-kind extra field (line 92) got reworded to add "as text in the fix field (never write it to disk)".
· risk: If the tool-layer gate ever fails to engage for a kind (see next finding), security/performance/code-quality finders carry the original ambiguous instruction with none of the defense-in-depth wording tests got.
· fix: Apply the same softening to line 157, e.g. fix (the concrete change, described as text — never written to disk).
· reference: PR body "Why" section; CLAUDE.md rule 1.

[Medium] No tracked step confirms agentType: 'audit-readonly' is actually honored by the harness
· dimension: 1 Correctness
· location: .claude/workflows/audit.mjs:223,237,262,271; .claude/workflows/prod-readiness.mjs:112,129
· issue: agentType has zero prior occurrences anywhere in this repo's history (verified via git log --all -S"agentType") — it's a brand-new option key on a host-provided agent() function with no local definition to confirm it's recognized. The PR body honestly admits it "can't be smoke-tested in the session that authored it," but its own follow-up list only proposes a /lesson capture, not a check that the gate engaged.
· risk: If the harness silently ignores an unrecognized options key, this merges reading as fixed while enforcing nothing — the same silent-miss failure mode, one layer up.
· fix: Add a concrete next-session step: confirm via workflow logs (or a deliberate "try to write a file" canary) that a spawned finder actually lacks Write/Edit/Bash, tracked alongside the planned /lesson.
· reference: PR body "Notes / verification"; CLAUDE.md rule 6.

Low / Info

None posted (signal gate).

Subagents

Agent Verdict
parity-checker N/A — no apps/web/**/apps/mobile/** changed
i18n-syncer N/A — no user-facing strings/locale JSON changed
contract-aligner N/A — no packages/shared/src/types/*/DTOs changed
security-reviewer N/A — orbit-api untouched
design-reviewer N/A — no UI files changed

Validation

Check Result
Lint / Type check / Tests / Build N/A — diff touches only .claude//.opencode/ harness config, no lintable/testable app surface; PR's own Build/Unit Tests/SonarCloud checks cover this separately

Deferred

  • Dimensions 8, 9, 10, 11, 13, 14: N/A — none of their surfaces (UI, web/mobile, i18n, shared/DTO contracts, orbit-api, user-facing feature surface) are touched by this diff.
  • Adversarial skeptic / cross-model second opinion: not run — gated to Critical/High, and none survived here.
  • All 4 changed files were reviewed against the committed PR diff — nothing left unreviewed.

What's good

  • Textbook gates-over-prose fix — read-only enforced by tool absence, not a trusted prompt.
  • New agent file + .opencode pointer match the existing convention exactly.
  • Every finder/skeptic/critic/gap-finder call site in both workflow files was updated — none missed.
  • Unusually honest "Notes/verification" section that doesn't overclaim the fix is proven.

Recommendation

APPROVE. No Critical/High blocks merge. Two Medium follow-ups worth doing in the next live session: even out the prompt-softening wording across all four audit kinds, and actually confirm agentType: 'audit-readonly' restricts tools in a real run rather than assuming the harness recognizes the new key.

@thomasluizon
thomasluizon merged commit c90433b into main Jul 10, 2026
19 checks passed
@thomasluizon
thomasluizon deleted the chore/harness-audit-readonly-gate branch July 10, 2026 23:11
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.

1 participant