Skip to content

Feat/batch approve proposals#111

Merged
plind-junior merged 8 commits into
vouchdev:testfrom
jsdevninja:feat/batch-approve-proposals
May 28, 2026
Merged

Feat/batch approve proposals#111
plind-junior merged 8 commits into
vouchdev:testfrom
jsdevninja:feat/batch-approve-proposals

Conversation

@jsdevninja

@jsdevninja jsdevninja commented May 26, 2026

Copy link
Copy Markdown
Contributor

What changed

Added vouch approve --batch <proposal-id>... so reviewers can approve multiple pending proposals in one command after the full set passes validation.

Closes #110

Why

Reviewing several already-inspected proposals currently requires repeated vouch approve <id> calls or shell pipelines. Batch approval makes that workflow first-class while preserving the existing review gate, self-approval checks, artifact writes, and audit behavior.

What might break

No breaking changes expected. Existing .vouch/ directories do not need migration, no files move, no persisted fields change shape, and the kb.* method surface is unchanged. Existing vouch approve <id> behavior remains supported.

VEP

Not required. This is an additive CLI-only change and does not alter the object model, kb.* methods, on-disk layout, bundle format, or audit-log shape.

Tests

  • make check passes locally (lint + mypy + pytest)
  • New / changed behaviour has a test
  • CHANGELOG.md updated under ## [Unreleased]

Summary by CodeRabbit

  • New Features

    • Batch approval to process multiple proposals with vouch approve --batch
    • Synchronization commands (sync-check, sync-apply) for directory/bundle reconciliation with conflict handling
    • JSON output option for pending proposals
    • Diff command to inspect changes between artifact revisions
    • Interactive review workflow with vouch review
    • Enhanced onboarding in vouch init
  • Bug Fixes

    • Fixed security vulnerability preventing review-gate bypass via markdown injection
  • Documentation

    • Added guides for sync and diff workflows with examples

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40f3686c-0087-4190-a66e-7b86c2648144

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

PR introduces five new CLI capabilities: batch approval (vouch approve --batch), interactive review workflow (vouch review with filtering and --dry-run), JSON output for pending proposals, deterministic KB synchronization (vouch sync-check/sync-apply), and artifact diff inspection (vouch diff). Includes a security fix to prevent review-gate bypass via session-summary markdown injection.

Changes

Batch approval and enhanced review workflow

Layer / File(s) Summary
Batch approval core logic and validation
src/vouch/proposals.py, tests/test_storage.py
New approve_many() function validates batch input (non-empty, no duplicates, unique targets), preflights all approvals, and approves each through shared _approve_validated(). Refactored approve() and new _validate_approval() delegate approval gating (pending status, self-approval policy, artifact overwrite prevention).
CLI batch approval command surface
src/vouch/cli.py, tests/test_cli.py
approve command updated to accept variadic proposal_ids with --batch flag; non-batch mode enforces single ID, batch mode routes to approve_many, and both paths use CLI error translation.
Pending JSON output and interactive review workflow
src/vouch/cli.py, tests/test_cli.py
pending command adds --json flag for structured output. review command redesigned with filtering (--type), limiting (--limit), --dry-run mode, and per-proposal interactive prompts (approve/reject/skip/quit with reason validation).
Session crystallization security fix
src/vouch/sessions.py, tests/test_sessions.py
Summary-page body now omits agent-controlled fields (task, agent, note), including only safe server-defined content (session ID, timestamps, artifact IDs). Audit event object_ids expanded to include summary page ID when written.
Documentation for batch approval and enhanced review
CHANGELOG.md, README.md, docs/review-gate.md
Updated CHANGELOG to document new features, README quick-start and CLI surface sections, and review-gate FAQ replacing "not yet available" with vouch approve --batch syntax and preflight validation guarantees.

Artifact diff inspection feature

Layer / File(s) Summary
Diff module contracts and core logic
src/vouch/diff.py
New module defines DiffError exception, FieldChange and ArtifactDiff dataclasses, and diff_artifacts(store, old_id, new_id) function: auto-detects artifact kind, validates both IDs and matching kinds, computes field-level changes (excluding metadata), and generates unified line diff for long text.
Diff unit and CLI tests
tests/test_diff.py
Comprehensive coverage for diff_artifacts on claims/pages, text diff rendering, identical artifact handling, error cases (unknown IDs, mismatched kinds), and CLI behavior (human output, --json JSON, "no differences" message, clean error display).
Diff feature design specification
docs/superpowers/specs/2026-05-25-vouch-diff-design.md
Design document defining read-only diffing, kind auto-detection, semantic-only field diffing, unified line-diff for long text, human/JSON output, error handling, TDD plan, and non-goals (no supersede-chain following, no entity/relation/source diffs).

Deterministic KB synchronization and diff CLI

Layer / File(s) Summary
Sync module data models and core operations
src/vouch/sync.py
New module defines IncomingFile, SyncConflict, SyncCheckResult dataclasses; implements source resolution (directory or .tar.gz bundle), preflight validation (manifest, per-file hash, content), and read-only sync_check() reporting new/identical/conflicting files with semantic vs. decided-proposal categorization.
Sync apply with conflict resolution
src/vouch/sync.py
Implements sync_apply(kb_dir, source_path, on_conflict, actor) applying non-conflicting incoming files: validates mode (fail/skip/propose), enforces hash/content validation at write, branches on conflicts (fail aborts, skip skips, propose writes JSON report), rebuilds index, emits audit event.
Diff CLI infrastructure imports
src/vouch/cli.py
Adds asdict import for JSON serialization and model imports (Proposal, ProposalStatus) for new command wiring.
CLI sync-check, sync-apply, and diff commands
src/vouch/cli.py
Wires new CLI commands: sync-check compares directories/bundles with JSON output, sync-apply applies changes with --on-conflict mode, diff shows human-readable or --json diffs between two revisions.
Storage read safety hardening
src/vouch/storage.py
Normalizes CRLF to LF before YAML frontmatter parsing; hardens read_under_root to reject directories upfront and use O_NOFOLLOW safe-open flags.
Sync and diff CLI tests
tests/test_sync.py, tests/test_diff.py
End-to-end test coverage for sync workflows (artifact syncing, config exclusion, conflict handling, bundle sources) and CLI JSON output validation.
Distributed sync documentation
docs/multi-agent.md, README.md
Adds "Distributed sync" section documenting sync-check/sync-apply reconciliation workflow, conflict modes, and config locality; updates README maintenance commands section.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

  • vouchdev/vouch#89 — Both PRs implement vouch pending --json in src/vouch/cli.py with matching test coverage for CLI JSON output.
  • vouchdev/vouch#84 — Both PRs add vouch review (interactive filtering/dry-run) and vouch diff (with --json support) CLI commands to the same file with corresponding test suites.
  • vouchdev/vouch#91 — Both PRs implement vouch sync-check/sync-apply deterministic KB synchronization and the src/vouch/sync.py module with conflict handling modes.

Poem

A rabbit hops through five new paths today, 🐰
Batch approvals streamline the way,
Diffs reveal what changed between,
Syncing teams with conflict screens,
Review, review—the workflow's here to stay! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes extend beyond batch approval: new diff command, sync commands, review enhancements, and session security fix are outside issue #110 scope. Isolate batch approval changes (approve_many, CLI --batch flag, tests) into a focused PR; move diff/sync/review features and session fix to separate PRs with their own issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.99% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/batch approve proposals' accurately summarizes the main change: batch approval feature for proposals.
Linked Issues check ✅ Passed The PR fully implements issue #110 requirements: batch approval command with validation, self-approval checks, artifact creation, and audit logging all preserved as specified.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@jsdevninja
jsdevninja changed the base branch from main to test May 26, 2026 17:22
@jsdevninja

Copy link
Copy Markdown
Contributor Author
image

@jsdevninja
jsdevninja force-pushed the feat/batch-approve-proposals branch from 500f2e1 to 8573218 Compare May 28, 2026 05:03
Comment thread docs/review-gate.md Outdated
galuis116 and others added 8 commits May 28, 2026 10:17
vouchdev#81)

The 'claims must cite sources' guarantee (README §'Why this exists'
point 3; CONTRIBUTING §'Things we won't merge') used to live only in
proposals.propose_claim, so every other write path silently accepted
Claim(evidence=[]) and landed an uncited claim:

  - store.put_claim direct: existence-check loop iterates zero times.
  - store.update_claim: writes the YAML without re-validating.
  - bundle.import_apply via _validate_content: defers to
    Claim.model_validate, which accepted evidence=[] because the
    model had no min-length constraint.

Add @field_validator('evidence') on Claim — raises ValueError when
the list is empty. Closes all three bypass paths in one place.

store.update_claim additionally re-validates via
Claim.model_validate(claim.model_dump()) before persisting, so
in-place mutation (c.evidence = []; store.update_claim(c)) raises
before the YAML hits disk — the field validator only fires at
construction time, not on attribute assignment.

Four regression tests:

  - test_claim_model_rejects_empty_evidence (tests/test_storage.py)
    — Claim(evidence=[]) raises pydantic.ValidationError.
  - test_put_claim_rejects_empty_evidence — store.put_claim raises;
    no claims/<id>.yaml is written.
  - test_update_claim_rejects_empty_evidence — in-place mutation
    + update_claim raises; the on-disk YAML is unchanged.
  - test_import_rejects_uncited_claim (tests/test_bundle.py) — a
    schema-valid bundle whose claim YAML has evidence: [] is
    rejected by import_check (schema validation issue) and
    import_apply raises before writing.

The existing guard in proposals.propose_claim becomes a redundant
user-facing error message and is left in place for the friendlier
CLI/JSONL error string. No on-disk-layout, schema, or
bundle-format change; data the model never should have accepted
now raises.
…hdev#82 review)

The new Claim.evidence min-citation validator (vouchdev#81) also fires when
claims are read back from disk. A KB that has a pre-existing
uncited claims/<id>.yaml from before the fix would otherwise crash
vouch lint / vouch doctor with a bare pydantic.ValidationError
deep in store.list_claims().

Add _load_claims_for_lint(), a per-file iteration that catches
pydantic.ValidationError (and any other load error) and surfaces
each bad file as a Finding with code='invalid_claim' and an
explicit repair hint: 'edit the YAML to add a citation, or delete
the file'. lint() also stops calling status() to populate counts —
status() calls the strict store.list_claims() which would re-raise
on the same files — and builds the counts dict inline from the
safely-loaded valid claims.

Regression test in tests/test_health.py:
  - test_lint_surfaces_legacy_uncited_claim_yaml_without_crashing
    hand-crafts a claims/legacy.yaml with evidence: []  (matches
    the on-disk shape an older buggy write path would have left),
    asserts vouch lint runs to completion, surfaces invalid_claim
    in findings with the repair-hint message, and that the
    well-formed sibling claim is still discovered.

CHANGELOG migration note expanded to describe the repair hint.
The new inline-built counts in lint() (from 5c881de) is a literal
dict with mixed value types (str/int/bool), which mypy correctly
inferred as dict[str, object] and rejected against the
HealthReport.counts: dict[str, int] annotation. The original
status() returned the same mixed dict via an untyped 'dict' return,
which masked the mismatch — the narrow type was effectively never
checked at the call site.

Widen counts to dict[str, Any] to match runtime reality. Also
tighten status()'s return annotation from 'dict' to 'dict[str, Any]'
for consistency. No caller does arithmetic on counts values; they
just echo or pass through, so the widening is risk-free.
…ouchdev#76)

crystallize wrote a durable Page directly through store.put_page,
embedding sess.task, sess.note, and sess.agent verbatim into the
rendered markdown body. The body was never gated by propose_page +
approve, so an agent calling kb.session_start(task=<payload>) and
getting any one claim approved via crystallize could land arbitrary
content into pages/. The page surfaces in kb.read_page, kb.list_pages,
kb.context, and (once vouchdev#60 is fixed) kb.search.

Restrict the summary body to fields the proposing agent cannot
influence: session id (server-generated), timestamps (server clock),
and the list of approved artifact ids. The agent-controlled fields
remain on the Session model itself and are still queryable, but no
longer promoted into a durable Page.

Also include summary_page_id in the session.crystallize audit
event's object_ids when a page is written, so vouch audit
truthfully attributes the write.

Adds two regression tests:
- test_crystallize_summary_page_does_not_leak_agent_controlled_fields
- test_crystallize_audit_event_records_summary_page_id
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.

Add batch approval for reviewed proposals

4 participants