wallet: client-side quote verification on cryptographically authenticated chunks
The Merkle bundle gives the wallet authentic chunk bytes, but the server still decides what audit_mode to claim. Run the existing verify_quotes() locally on the bundle's chunks so the wallet has an independent verdict that doesn't trust the server's verifier at all. `VerifiedAnswer` now carries `local_audit_mode`, `local_n_verified`, `local_verifier_method` alongside the server's audit_mode. They can legitimately differ (server's context is larger), but a wallet-side STRICT against a server-side UNGROUNDED would be a real "server lied about not finding grounding" signal — exactly what the SPV pattern exists to catch. Opt out with `client.ask(q, verify_locally=False)` for pure-stdlib SPV ports that can't load the verifier.
This commit is contained in:
parent
7e160584b6
commit
296017295f
3 changed files with 94 additions and 8 deletions
|
|
@ -221,6 +221,48 @@ def test_server_healthz_and_snapshot_root(corpus_db: Path, tmp_path: Path):
|
|||
httpd.shutdown()
|
||||
|
||||
|
||||
def test_client_local_verify_quotes_runs_on_authenticated_chunks(
|
||||
corpus_db: Path, tmp_path: Path
|
||||
):
|
||||
"""verify_locally=True re-grounds the answer on the bundle's chunks
|
||||
(which are now cryptographically authenticated). A verbatim quote
|
||||
answer reaches STRICT locally; a fabricated answer falls to
|
||||
UNGROUNDED locally even if the server claims STRICT — that's the
|
||||
'wallet doesn't have to trust the server's verifier' property.
|
||||
"""
|
||||
from arborist.wallet.client import WalletClient
|
||||
from arborist.wallet.server import WalletServer, serve_in_thread
|
||||
|
||||
verbatim = (
|
||||
'"eight forms of capital include living, social, and intellectual"'
|
||||
)
|
||||
|
||||
def _factory():
|
||||
return StubClient(answer=f"According to the source: {verbatim}.")
|
||||
|
||||
wallet_server = WalletServer(
|
||||
qa_db=tmp_path / "qa.db",
|
||||
single_db=corpus_db,
|
||||
chat_client_factory=_factory,
|
||||
)
|
||||
httpd, _t, base_url = serve_in_thread(server=wallet_server, port=0)
|
||||
try:
|
||||
conn = connect(corpus_db)
|
||||
try:
|
||||
snap, _ = compute_snapshot_root(conn)
|
||||
finally:
|
||||
conn.close()
|
||||
client = WalletClient(server_url=base_url, trust_anchor=snap)
|
||||
verified = client.ask("What are the forms of capital?")
|
||||
# Local verifier ran and reached STRICT on the authenticated chunks.
|
||||
assert verified.local_audit_mode == "STRICT"
|
||||
assert verified.local_n_verified is not None
|
||||
assert verified.local_n_verified >= 1
|
||||
assert verified.local_verifier_method in {"quote", "span", "entity", "paraphrase"}
|
||||
finally:
|
||||
httpd.shutdown()
|
||||
|
||||
|
||||
def test_server_ask_end_to_end_returns_verifiable_bundle(
|
||||
corpus_db: Path, tmp_path: Path
|
||||
):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue