Skip to content

feat(qsearch): Nano q-search service for Predator Drive - #45

Merged
AetherAI3 merged 29 commits into
mainfrom
feat/v2-l3-qsearch-service
Oct 23, 2026
Merged

AetherAI3 merged 29 commits into
mainfrom
feat/v2-l3-qsearch-service

Conversation

@AetherAI3

Copy link
Copy Markdown
Owner

Adds nano_qsearch, the q-search service that Predator Drive's NanoRuntimeQSearchBackend calls (V2 lane L3, gate G-W3). It takes the exact QSearchJobV1.to_payload() body on POST /qsearch/v1/submit and answers with a predator.qsearch.receipt.v1 that Predator's own QSearchReceiptV1.from_payload accepts. The wire contract is unchanged.

  • Authenticated, replay-safe requests. X-Aether-Qsearch-Auth carries an HMAC-SHA256 over method, path, body hash, timestamp and nonce, with its own key file. The window is ±120 s and nonces are durable. Unauthenticated or replayed requests get a generic 401 and no receipt.
  • Sealed receipts. Every receipt is HMAC-sealed over Predator-identical canonical JSON with a separate key. A golden vector produced by Predator's canonical_digest pins the encoding.
  • Durable and idempotent. A SQLite store is keyed by job_digest, so an identical resubmit returns the stored receipt. After a crash, restart re-runs an accepted job once, with the same seed and the backend recorded at accept time. Hardware jobs are never run from recovery. The data directory is locked to one instance.
  • Real engines, crash-isolated. ATLAS build_nucleus_program compiles ordinary and joint5 (via find_nuclei), transpiles to Heron {cz, rz, sx, x} on heavy-hex and keeps the smaller native 2Q count. Qiskit Aer then samples on statevector (n ≤ 20) or MPS, in a child process with OMP_NUM_THREADS=1, five tries and a per-job wall budget. A failure becomes NOT_RUN with a class-name reason. MPS reports the peak bond dimension from Aer's log. Every receipt here is classical simulation.
  • IBM refuses by default. It needs AETHER_AQRC_HW_GO=1, an authority file, QPU seconds and a Fez target. Even then the ATLAS campaign_runtime delegate refuses, because no q-search purpose is qualified there yet.
  • Packaging. The code lives in a top-level nano_qsearch/ package beside nano/, because tests/test_determinism_guards.py keeps clocks, environment, subprocesses and third-party imports out of nano/. The base install stays dependency-free. qiskit 2.3.1, qiskit-aer 0.17.2 and numpy come with the new aether-nano[quantum] extra, and the CLI is nano-qsearch-serve. Operator guide: docs/qsearch-service.md. Plan, evidence and hand-off notes: docs/superpowers/plans/2026-10-21-l3-qsearch-service.md.

Needs a Predator change (raised in the hand-off notes, not made here). Predator's default opener sends no auth header, so it gets NOT_RUN transport: HTTPError until Predator gains a signing opener and a request-key setting.

Tests. GitHub Actions is not running on private Aether repos right now, so these are local results. They were run from the repo root on Windows with system Python 3.12.10, qiskit 2.3.1, qiskit-aer 0.17.2, SQLite 3.49.1, ATLAS feat/qcompiler-find-nuclei and Predator-M origin/main 17ee3368:

$env:NANO_QSEARCH_ATLAS_PATH = "<aether-atlas checkout>"; $env:NANO_QSEARCH_PREDATOR_PATH = "<Predator-M checkout>"; $env:OMP_NUM_THREADS = "1"
python -m pytest -q -W ignore::DeprecationWarning --ignore=tests/test_provenance.py
1468 passed, 1 skipped
  • tests/test_provenance.py has 7 failures that predate this branch: the installed aether_protocol_c rejects float fields. Neither that file nor nano/bridge is touched.
  • In a plain venv with only pytest and pip install -e ., the suite gives 1370 passed, 4 skipped. The engine tests skip there.
  • A live run of python -m nano_qsearch produced:
    • a signed pair02 POST that Predator accepted (statevector, joint5, agree true);
    • a resubmit that came back from the store in 0.11 s;
    • after killing the server mid-MPS-job and restarting it, recovered=1, a single re-run on the recorded backend, and attempts 2;
    • exit 2 for a second instance on the same data directory.

ATLAS counterpart: AetherAI3/AETHER-ATLAS#177.

🤖 Generated with Claude Code

AetherAI3 and others added 29 commits October 21, 2026 13:35
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
pair02 on heron_heavy_hex (d3, 19 physical qubits), opt 3, seed 11:
ordinary logical_2q 30 / native_2q 112 / native_depth 85;
joint5 (four greedy nuclei) logical_2q 46 / native_2q 87 / native_depth 66.
auto keeps joint5.

Deviations from the plan code, both forced by qiskit 2.3.1:
- CouplingMap.distance is a read-only method, so the code distance is
  recovered from the map size by _heavy_hex_distance (the plan's fallback).
- Unnamed circuits get a process-global counter name that QPY serializes,
  which made the plan digest differ between two compiles of the same job;
  the QPY hash is taken over a copy with a fixed name.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Two compiles of pair02 produced equal ISA circuits but different QPY
bytes: the TranspileLayout stored in QPY places idle ancilla qubits
differently from run to run (also across processes). The qpy_sha256 is
now taken over a layout-free copy (same registers, instructions, global
phase, metadata, fixed name). Checked stable over 8 compiles in each of
two processes for pair02 and job_4var; measurement_map in the plan still
records the physical qubit read into each classical bit.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Engine code is the plan's Step 3 unchanged. Measured (qiskit-aer 0.17.2):
pair02 joint5 on heavy_hex_d3: statevector tvd_ideal 0.0288, MPS
tvd_ideal 0.0339 (|diff| 0.0051 <= 0.01), MPS chi_reached 6 of 64,
mean_energy_sample -0.7035 vs mean_energy_ideal -0.7072, best sample
energy equals classical_min_energy -18.0327 (to 4e-15).
job_4var: tvd_ideal 0.0475 (sv) / 0.0664 (mps), chi_reached 2, best -1.6.
One CLI child run (job_4var, statevector) about 4.4 s wall.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
The nano package is deterministic by contract (tests/test_determinism_guards.py: no clock, env, subprocess or network, one optional third-party import). The q-search service is a host process, so it lives beside nano/, not inside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Job parsing: overflowing ints are QUBO errors instead of OverflowError, symmetry is exact equality, seeds may be negative (Predator allows them; the service refuses out-of-range seeds with a receipt), and parse_job is split into per-section helpers with unchanged error codes.

Receipts: ok_receipt re-derives every sample energy from the job, requires best to be their minimum and not below the classical floor, and raises ReceiptAuditError otherwise. verify_seal maps an uncanonical body to SealError. NOT_RUN/REFUSED reasons are capped at 200 chars.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JobStore.complete is one conditional UPDATE (KeyError if unknown, ValueError if already DONE, the stored receipt untouched); mark_attempt raises KeyError for an unknown digest; busy_timeout=5000. Auth tests pin the AuthError code per tamper case. conftest appends sibling paths and fails loudly on a non-directory; regen.py writes LF and exits 2 without NANO_QSEARCH_PREDATOR_PATH. Docstrings: MemoryNonceStore is tests-only; canonical refuses Enum/dataclass on purpose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd recovery

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pyproject: nano-qsearch-serve script, quantum extra (qiskit 2.3.1, qiskit-aer 0.17.2, numpy), engine/integration markers. docs/qsearch-service.md: config and caps, auth, responses, chi_source, recovery, package location, and the Predator-side signing opener (exercised by a test against a live service). Also: the HTTP handler drains small bodies on 404 so Windows does not reset the client, and the 413 test sends headers only (an unread oversized body can reset the connection by design).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…afe child runs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r bind

Request socket timeout (NANO_QSEARCH_REQUEST_TIMEOUT_S, default 30 s); exclusive qsearch.lock per data dir (DataDirLocked, exit 2); prepare() binds before recover(); a complete() ValueError returns the stored DONE receipt; nonce-store errors are 503; BoundedSemaphore cap on runtime calls (NANO_QSEARCH_MAX_CONCURRENT, default 2); runtime kind stored at accept (migrated jobs.kind column) and used by recovery; 404 for every non-POST method, empty-body HEAD; submit gets SUBMIT_PATH; generic 401 body with the code logged only when verbose. Tests for each, plus the review's missing cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nup pinned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e one line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also: the recovery reason for hardware jobs now reads 'never run from recovery', which is true whether or not the run had started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strip legacy IBM and Predator key variables from the engine child, report unreadable key files by class only, document logical-circuit sampling, the Fez target dependency and the IBM delegate stub, replace local paths in the plan, and add a changelog entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AetherAI3
AetherAI3 merged commit b771d04 into main Oct 23, 2026
0 of 18 checks passed
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.

1 participant