DaveHarness approval preflight; wire file.edit - #51
Merged
Merged
Conversation
An approval-required ToolDefinition may now set preflight(arguments, context) -> str | None. DaveHarness runs it after schema validation and before the approval pause, on both the lifecycle (state_engine) and legacy executor paths. A nonempty string refuses the call as an ordinary tool error whose handler never ran: status "error", termination "denied", no pending call or nonce, one tool call and one error, call ID in the executed ledger, events policy_decision(pause) + tool_result(error, preflight_refused). Anything else, an exception, an oversize refusal, or the tool deadline pauses as before, so a preflight spares an approval but never authorizes an effect. The preflight's provenance (and preflight_version for opaque state) joins the fingerprint only when set, so definitions without one keep their exact fingerprints. Registration rejects a preflight on a tool that needs no approval, a coroutine, a callable that cannot take (arguments, context), and a version without a preflight. file.edit uses preflight_file_edit -> davellm_edit.check_edit, which shares the handler's pre-write checks (admission, file type, links, UTF-8, old_text count, size) and returns the handler's own message. The approved edit still checks everything again. Catalog fixture pins the preflight like a handler (extended_preflight_code); qualified entries are unchanged. Public API snapshot, capabilities manifest, tool catalog, H2/H9 docs, README and CLAUDE.md updated. The H9 runner test now expects edit_outside_root_approved to end before approval; the corpus, runner and thresholds are unchanged.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Oct 1, 2026
…red planning Keep existing Notion handler provenance, regenerate capabilities, and verify overlap refusal before approval and again at execution.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An approval-required
ToolDefinitioncan now declare an optional preflight:DaveHarness runs it after schema validation and canonicalization, before the approval pause, on both approval paths (lifecycle
state_engine._drain_callsand the legacyengine._process_calls). Contract:docs/DAVEHARNESS_PREFLIGHT.md.status: "error",termination: "denied", empty result, the refusal aserror. No pending call or nonce. It counts as one tool call and one error, the call ID enters the executed ledger, and the remaining calls in the same step continue. Events:policy_decision(pause, approval_required)+tool_result(error, preflight_refused), with noapproval_requiredevent.denied, noterror: DaveHarness already usesdeniedfor calls refused before their handler runs (schema, policy).errorwould claim the handler ran and failed.None, any non-string,"", an exception, a refusal larger than the tool output budget, or missing the tool deadline (min oftimeout_secondsand the run deadline) → the call pauses exactly as before. A preflight can spare an approval but never authorize an effect, and the handler still checks everything after approval.HOST_RUN_CONTEXTis visible). It is never run for pre-approved calls, on/tools/execute, or again after approval.approval_required, a coroutine, a callable that can't take(arguments, context), andpreflight_versionwithout a preflight.Fingerprints
The preflight's provenance (module, qualname, code digest, stable state;
preflight_versionfor opaque state) is added to the fingerprint payload only when set. Every definition without a preflight keeps its exact fingerprint;test_a_definition_without_a_preflight_keeps_its_fingerprintrecomputes the old algorithm to prove it. Qualified catalog entries intests/fixtures/davellm/tool_catalog.jsonare byte-identical.file.editgainspreflight/preflight_version, and the newextended_preflight_codepins its placement and source, like a handler.file.edit
preflight_file_edit(app.py, below every handler) →davellm_edit.check_edit, which shares the handler's pre-write checks via the extracted_request/_plan: path admission, file type, hard links, UTF-8,old_textcount (CRLF-aware), no-op, and size. It returns the handler's own message. The approved edit still runs every check again; the file can change after approval.It doesn't expose new information: within the root, the extended read tools behind the same flag already give the model this without approval, and every refusal outside the root is the same fixed
PathNotAllowedmessage.Notion tools (PR #48)
Not wired: #48 is still an open draft. Once it merges, the hook takes the Notion checks directly: the per-run ledger is reachable from the preflight through
HOST_RUN_CONTEXT.Needs a decision: H9 edit cases
edit_outside_root_approvedis now refused before the operator is asked, so the runner records 0 approvals instead of 1. Status, tool sequence, task pass and 0 unauthorized effects are unchanged. The runner unit test expectation is updated. The corpus, runner, and thresholds are not changed. "Approval never extends the root" is still covered offline by the newtest_a_path_moved_outside_the_root_after_the_request_still_writes_nothing. With real models, a wrongold_textnow gets an immediate retryable refusal instead of an approval card, so edit-case results from before this change (2026-09-25 run) aren't directly comparable. This is noted indocs/DAVEHARNESS_H9_LIVE_EVALUATION.md. Whether to rework that case is your call.Other changes
tests/fixtures/daveharness/public_api.json:ToolDefinitiongains the two optional fields (deliberate snapshot update).daveharness.events: new reason codepreflight_refused.preflight; regenerated withscripts/generate_capabilities_manifest.py.file.editlifecycle tests now hold one event loop (with client), the repo's pattern for runs that do threaded work; the pause path now does real I/O in a thread.1.0.0-rc.1(additive optional field).Checks (local, Python 3.14.3)
All required checks from CLAUDE.md pass:
py_compile,compileall,mypy daveharness(strict, clean),pytest -q789 passed, 1 skipped,node --check,node --test(4/4),bash -n,npm ci,npm ls,npm audit(0 vulnerabilities),git diff --check, andgenerate_capabilities_manifest.py --check.