Repository navigation
Conversation
…l-closed fixes + identity fence Integrate the OpenAPPA information-flow engine into pawan's agent boundaries (design: /opt/tmp/appa-integration-design.md; survey: /opt/tmp/appa-stack-agents.md §2): pawan-core: - appa git dep at rev 1c52575 (v0.31.1+20), default-features=false, plus appa-runtime-api and appa-eventlog at the same rev (the wire vocabulary and the in-memory LogStore are not re-exported by `appa`; measured). - agent/appa.rs: the embedded host — Adapter (host/pawan/<tool>, control tool spelled execute_remedy_plan), hosted-composed config, Memory log, from_documents/open constructors, check_call/check_result/execute_remedy, [appa] config section (off by default). - execute.rs: pre-call check inside check_tool_permissions (a denied call never enters the tool's execute path); post-call check after tools.execute returns, before the record is built (withheld/refused results never reach the model; a sanitized derivation crosses in place of the raw); control-tool routing to execute_embedded_remedy; coordinator mode fails closed under an active guard (it bypasses the hooks by construction). - Measured permission defects fixed: mutating tools default to Prompt and a missing approval callback is now a HARD DENY (was auto-approve, execute.rs:842-847); the mutating() overrides are real for write_file, edit_file_lines, insert_after, append_file, git write tools, ast_grep, sd, deagle_map, mise, and MCP bridges. - rusqlite 0.34 -> 0.40 (aligns with appa-eventlog's rusqlite). pawan-api: - Identity fence: every agent-running and session route requires a verified identity (bearer or X-API-Key). Static token (PAWAN_API_TOKEN) or dims-auth composition (PAWAN_DIMS_AUTH_URL -> its measured /v1/validate contract). Unconfigured refuses with 503; an auth-service outage refuses with 503 — never an open serve. CORS is allow-list driven (PAWAN_API_CORS_ORIGIN), no longer allow-origin(Any). /api/health stays open (liveness by design). BLOCKER (not papered over): the graph does not resolve. deagle-core (pinned ^0.34 rusqlite at every published version 0.1.4-0.3.0, measured via the crates.io index) and appa-eventlog (rusqlite 0.40) cannot coexist: cargo's links="sqlite3" rule forbids two libsqlite3-sys versions. Fix before merge: a deagle-core patch/fork on rusqlite 0.40. All decision shapes the tests assert were measured against the real engine via a probe harness (DenyCall on attention=["blocked"], Refuse on a closed-port battery, AllowCall on the healthy control, ReplaceOutput vs DeliverValue on the confined-result arms).
|
Status: BLOCKED on a dependency-graph conflict — the integration is complete but cannot compile until it resolves. Recorded here so this PR is not mistaken for merge-ready. The conflict (measured): The fix being routed: a deagle-core rusqlite-0.40 release with its own build/test proof. Until it lands, this PR stays blocked with the integration intact — no [patch.crates-io] band-aid, because that would diverge the estate's rusqlite version per repo. What is DONE and measured in this PR (via a path-dep probe harness against the real pinned engine, since the graph conflict prevents the in-repo test run): the pre-call hook in front of every permission layer (denied calls never enter the tool's execute path), the post-call hook before the record build and history write, no-callback mutating tools now HARD DENY (was auto-approve), What lands WITH the graph fix: the three-invariant Rust tests, config/appa.test.toml, the policy-tests/ replay traces, and the CI gate (describe --check + replay). Recorded unwired: spawn_agent subprocess loops, heal()'s direct cargo calls, post-dispatch bash/sd/ast_grep internals, MCP server-side effects, model-internal actions, and append_file's missing validate_file_write (that one is a pre-existing defect deserving its own issue — noted in the PR). |
… gate, sqlite resolve
The graph conflict from the first push is resolved: pawan-core's rusqlite
0.34 -> 0.40, deagle-core 0.3.1 / deagle-parse 0.3.0 (rusqlite ^0.40,
merged upstream at dermacs/deagle#26 d16728c). `cargo tree -i libsqlite3-sys`
now shows exactly one version (0.38.2 via rusqlite 0.40.2) serving
appa-eventlog, the tasks feature, and deagle-core.
Landed pieces:
- agent/appa_tests.rs — the three invariants, each with its healthy control,
encoding the shapes measured against the real engine at the pinned rev:
* invariant 1: requires={attention=["blocked"]} -> DenyCall(offers=0);
the canary tool's execute path records ZERO invocations and the canary
file is absent; the model reads the engine's feedback.
* invariant 2: confined result, sanitizer DOWN -> ReplaceOutput ("no
registered sanitizer answered") — the raw body never reaches the model;
sanitizer LIVE -> DeliverValue("scrubbed") — the derivation crosses.
* invariant 3: battery on a closed port -> Refuse, zero invocations;
the same battery answering -> AllowCall, tool executes, result admitted.
* plus the remedy flow (DenyCall(offers) -> PassControl -> Authorized ->
AllowCall -> DeliverValue), the undeclared-tool refusal, the
missing-callback hard deny, the Prompt defaults, the real mutating()
overrides, and fail-closed-at-open.
- execute.rs — the remedy is VOUCHED by the control call before
execute_embedded_remedy (an unvouched plan reads as Refused{Unvouched});
the pre-call check identifies the call through the adapter (under
ToolNaming::Canonical a raw spelling reads as "not declared").
- config/appa.test.toml + policy-tests/ + policy-tests-negative/ +
scripts/appa-gate.sh + the appa-gate CI job: describe --check rc=0,
replay pass rc=0 (2 ok), the negative control rc=1 ("got deny, want
allow") — the harness can fail. The CLI is built from the pinned rev in
CI, never install.sh.
- pawan.example.toml documents the [appa] section (off by default).
- tools/edit_tests.rs — the stale default-Allow assertion updated to the
Prompt posture.
Evidence (rustc 1.95.0): cargo test --workspace — 969 pawan-core lib tests
+ all other binaries/integration suites green; scripts/appa-gate.sh PASS.
Repo-wide cargo fmt --check clean.
The Doc job builds with -D warnings plus --document-private-items, so both rustdoc::broken-intra-doc-links and rustdoc::private-intra-doc-links are hard errors: the module doc linked crate::agent::execute (resolves only in private-items mode) and a nonexistent AppaGuard::failed. Both are now plain code spans referencing the real symbols; cargo doc with the CI's exact flags is clean.
|
Ready for review — final state (this supersedes the earlier BLOCKED status). What to review: the appa gate at pawan's agent-loop seams (pre-call in CI on the head (90f8a53) — all six green: Check & Lint, Doc, OpenAPPA policy gate (describe rc=0, replay pass rc=0 ×2, negative control rc=1 — the harness discriminates), Test (nextest) — 969 pawan-core lib incl. 11 guard tests, 59 integration, 42 pawan-api incl. 8 identity-fence tests, 46 pawan-mcp, 0 failures, clippy 0 warnings, fmt clean — macOS TUI smoke, cargo audit. (PR #141 adds the sixth job your protection requires: Coverage + CRAP.) The three invariants, each executed with its control:
Honest limitations carried in the body (not silently closed): the dims-auth composition is verified against the real Merge sequencing: this PR is behavior-neutral and needs your approval like any protected merge; #141 (the required Coverage + CRAP gate) is independent and can land first — once it's on main I'll update this branch so the gate runs on its head too, then this merges with all seven jobs green. The production policy configuration remains a separate, deliberate act pending the owner's decisions on #316. |
Status: LANDED — graph resolved, all pieces implemented, full suite executed. The earlier BLOCKED state is closed: the last conflicting sqlite edge is gone (pawan-core's rusqlite 0.34→0.40 in one cast;
dermacs/deagle-core0.3.1 published with rusqlite 0.40 —cargo tree -i libsqlite3-sys= exactly one, 0.38.2).What this PR is
The OpenAPPA guardrail embedded as a lib crate at pawan's agent-loop seams: the pre-call check inside
check_tool_permissions(before the tool's execute path is ever entered) and the post-call check aftertools.executereturns, before the result reaches the model. The engine answers one question each way — is this data allowed to go to this destination? and may this result re-enter the conversation?The merge changes no production behavior. The guard is off by default:
[appa] enabled = false(or the section absent) leaves pawan's permission gates exactly as they were; activation needs an explicitenabled = trueand apolicy_path. The production policy configuration is a separate, deliberate act awaiting the owner's decisions — this PR ships the mechanism, its proof, and its gate.Evidence (all executed on this head)
cargo test --workspaceALL GREEN — 969 pawan-core lib (incl. 11 guard tests), 59 integration, 42 pawan-api (incl. 8 identity-fence tests), 46 pawan-mcp, the cli suites, doc tests — 0 failures;cargo clippy -p pawan-core -p pawan-api -p pawan-mcp0 warnings;cargo fmt --checkclean.ReplaceOutput"no registered sanitizer answered"); the healthy control delivers the sanitized derivation (DeliverValue("scrubbed")).Refusewith zero invocations; the same battery answering ⇒AllowCall+ the tool executes.mutating()overrides, fail-closed-at-open.scripts/appa-gate.sh—describe --checkrc=0, replay pass rc=0 (2 ok), negative control rc=1 ("got deny, want allow" — the harness discriminates). The CI job clones the pinned engine rev and builds the CLI from source, never an installer. CI on the head: Check & Lint, Doc, OpenAPPA policy gate, Test (nextest), cargo audit, macOS TUI smoke — all success.1c52575176330bc88a3c346abe678af5d42083b3(v0.31.1, MIT, edition 2024;appa+appa-runtime-api+appa-eventlog,default-features = false,Backend::Memoryfor embeds — the sqlite format is version-bound).Behaviors found and fixed while landing (recorded in tests)
execute_embedded_remedy(unvouched =Refused{Unvouched}) —take_remedynow vouch-then-executes.ToolNaming::Canonical— a raw spelling reads as "not declared").Honest limitations (standing gaps, none silently assumed covered)
/v1/validateCONTRACT (auth-src ff314e1: request{token, required_role, resource_id}→ response{user_id, email, roles}on 2xx; 401 invalid_token; 503 store outage), not against a live dims-auth instance — the fence's tests mock that exact contract. The first live wire-up re-pins live response fields againstValidateResponse.heal()'s direct cargo calls, post-dispatch bash/sd/ast_grep internals, MCP server-side effects,append_file's missingvalidate_file_write, coordinator mode runs without the guard.