Skip to content

feat: hydrate validate (dry-run coherence findings) - #72

Merged
rennehan merged 3 commits into
mainfrom
feat/cli-validate
Jul 25, 2026
Merged

feat: hydrate validate (dry-run coherence findings)#72
rennehan merged 3 commits into
mainfrom
feat/cli-validate

Conversation

@rennehan

Copy link
Copy Markdown
Contributor

Summary

Adds hydrate validate — the agent's dry-run gate in the walk → validate → commit loop.

It lowers the current stage into the same typed delta batch commit builds (staging::lower), POSTs it to POST /v1/branches/{id}/validate, and prints the server's coherence report. Unlike commit it is non-mutating: it never applies the batch and, critically, never clears the stage.

Exit code

  • 0 when there are no error-severity findings.
  • New distinct code 5 (exit::VALIDATION) when there are — so an agent can gate a loop:
    hydrate validate && hydrate commit
    
  • The findings always print regardless (human list, or the verbatim {valid, findings[]} with --json); the exit code is only the pass/fail signal. A transport/parse failure keeps its existing code (network 6, generic 1, ...), so "found errors" is never confused with "couldn't reach the service". 5 is unused by the existing set (0/1/4/6).

Backend dependency

Depends on the merged C04 coherence-findings backend contract. POST /v1/branches/{id}/validate now returns {version, project_id, branch, valid, findings[]} with Finding {code, severity, locator, message}, replacing the previous first-failing-delta shape.

This PR re-vendors openapi.json (verbatim from the backend's regenerated spec) and regenerates wire/ via scripts/regen-wire.sh to pick up the new Finding / ValidateResponse schemas. wire/ is generated, not hand-edited; the CI wire-drift check is idempotent against the committed spec.

Changes

  • src/cmd/validate.rs — new verb (prepare/render/exit-code split, all unit-tested).
  • src/client/mod.rsvalidate_deltas(branch_id, body) wrapping the generated call (mirrors apply_deltas).
  • src/exit.rsSUCCESS = 0, VALIDATION = 5.
  • src/cli.rs, src/cmd/mod.rs — verb wiring (finish_with_code maps the handler-chosen success code).
  • src/cmd/guide.rs, README.md, tests/help.rs — surface/docs/help updates.
  • openapi.json, wire/** — re-vendored spec + regenerated client.

Tests

TDD. New coverage: clean report → exit 0 + valid in both modes; error findings → exit 5 (distinct from conflict/network/generic) + full findings printed; --json vs human parity; warning-only findings are advisory (still pass); mixed findings gate on the error; and prepare lowers the stage without clearing it (the non-mutating invariant). cargo test (326 unit + help suite), cargo fmt -p hydrate -- --check, and cargo clippy --workspace --all-targets -- -D warnings all green.

Add `hydrate validate`: it lowers the current stage to the same typed delta
batch `commit` builds, POSTs it to `POST /v1/branches/{id}/validate`, and prints
the server's coherence report — without committing and, critically, without
clearing the stage (it is a non-mutating server dry-run).

Exit code: `0` when there are no error-severity findings, and a new distinct
code `5` when there are, so an agent can gate a loop: `hydrate validate &&
hydrate commit`. The findings always print (human list, or the verbatim
`{valid, findings[]}` with `--json`); the exit code is only the pass/fail
signal. A transport or parse failure keeps its own existing code.

Re-vendor `openapi.json` and regenerate `wire/` to pick up the updated validate
contract — the response is now `{version, project_id, branch, valid, findings}`
with `Finding {code, severity, locator, message}` — replacing the previous
first-failing-delta shape.

Also wires the verb into the CLI surface, adds `client.validate_deltas`, and
updates the guide, README, and the `--help` verb test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rennehan rennehan added the enhancement New feature or request label Jul 25, 2026
@rennehan rennehan self-assigned this Jul 25, 2026
rennehan and others added 2 commits July 25, 2026 08:50
The server is the sole authority for validation. Gate `hydrate validate`'s
exit code and human verdict on the server's authoritative `response.valid`
rather than re-deriving them from a client-side error-severity scan, closing
the silent-divergence trap where `valid:false` with no error-severity finding
(a future warning-arm the contract anticipates) would exit 0 / print "Valid"
while `--json` reported `valid:false`.

- exit_code: exit VALIDATION (5) iff `!response.valid`, else SUCCESS (0);
  error_findings kept only for the displayed count.
- Human verdict line driven by `response.valid`, not the severity scan.
- Fail loud: `disagreement_warning` surfaces (to stderr, in both modes) any
  conflict between the server verdict and the presence of error-severity
  findings instead of silently trusting one side.
- render: borrow findings via `as_deref` instead of cloning the whole vec.
- CLAUDE.md: add exit code 5 to the stable-exit-codes enumeration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sync the vendored spec to the backend after the internal codename was
scrubbed from the Finding schema description (hydrate-sh/app#228), and
regenerate wire/ so the generated client's doc-comment is clean too. No
contract shape change — description text only. Keeps the public artifact
free of internal codenames (caught by this PR's fan-out review).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rennehan
rennehan merged commit e7a6172 into main Jul 25, 2026
3 checks passed
@rennehan
rennehan deleted the feat/cli-validate branch July 25, 2026 12:54
@rennehan rennehan mentioned this pull request Jul 26, 2026
rennehan added a commit that referenced this pull request Jul 29, 2026
* feat: hydrate validate (dry-run coherence findings)

Add `hydrate validate`: it lowers the current stage to the same typed delta
batch `commit` builds, POSTs it to `POST /v1/branches/{id}/validate`, and prints
the server's coherence report — without committing and, critically, without
clearing the stage (it is a non-mutating server dry-run).

Exit code: `0` when there are no error-severity findings, and a new distinct
code `5` when there are, so an agent can gate a loop: `hydrate validate &&
hydrate commit`. The findings always print (human list, or the verbatim
`{valid, findings[]}` with `--json`); the exit code is only the pass/fail
signal. A transport or parse failure keeps its own existing code.

Re-vendor `openapi.json` and regenerate `wire/` to pick up the updated validate
contract — the response is now `{version, project_id, branch, valid, findings}`
with `Finding {code, severity, locator, message}` — replacing the previous
first-failing-delta shape.

Also wires the verb into the CLI surface, adds `client.validate_deltas`, and
updates the guide, README, and the `--help` verb test.

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

* fix: honor server valid verdict; doc exit code 5

The server is the sole authority for validation. Gate `hydrate validate`'s
exit code and human verdict on the server's authoritative `response.valid`
rather than re-deriving them from a client-side error-severity scan, closing
the silent-divergence trap where `valid:false` with no error-severity finding
(a future warning-arm the contract anticipates) would exit 0 / print "Valid"
while `--json` reported `valid:false`.

- exit_code: exit VALIDATION (5) iff `!response.valid`, else SUCCESS (0);
  error_findings kept only for the displayed count.
- Human verdict line driven by `response.valid`, not the severity scan.
- Fail loud: `disagreement_warning` surfaces (to stderr, in both modes) any
  conflict between the server verdict and the presence of error-severity
  findings instead of silently trusting one side.
- render: borrow findings via `as_deref` instead of cloning the whole vec.
- CLAUDE.md: add exit code 5 to the stable-exit-codes enumeration.

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

* chore: re-vendor scrubbed openapi.json + regen wire

Sync the vendored spec to the backend after the internal codename was
scrubbed from the Finding schema description, and
regenerate wire/ so the generated client's doc-comment is clean too. No
contract shape change — description text only. Keeps the public artifact
free of internal codenames (caught by this PR's fan-out review).

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant