Skip to content

fix: repair the 35 defects of the 2026-09-16 read-only audit and connect the missing service paths - #126

Merged
bitcoinuniverseadmin merged 28 commits into
developfrom
fix/audit-20260916-go
Sep 17, 2026
Merged

bitcoinuniverseadmin merged 28 commits into
developfrom
fix/audit-20260916-go

Conversation

@bitcoinuniverseadmin

Copy link
Copy Markdown

Response to audits/chatgpt-readonly-20260916-1919/mempool_HANDOFF_2026-09-16.zip. Every mempool-side defect (BE-DEF-001 to 020, FE-D01 to D06, RC-ORIGIN-001/002, RC-PACKAGE-001) is repaired here; the overlay defects PO-D01 to D05 are in bitcoinuniverseio/backend-apis#197 and the DRC-20 decision authority in bitcoinuniverseio/ord-dogecoin#55.

Backend

  • Indexer scheduling and typed task outcomes (BE-DEF-007/008/009); admin task/reindex readback only verifies a completed task with a persisted checkpoint.
  • WebSocket track-txs looks the transaction up on the owned node and never reports an unknown id confirmed (BE-DEF-010); JS template selection accumulates block sigops (BE-DEF-011).
  • Admin runs: persisted cancellability, initial lease + owner token, lock-conflict terminal state, 30 s heartbeat, structured reconciliation (BE-DEF-003/004/006/013); shared replay fence in MySQL/Redis that fails closed (BE-DEF-005).
  • Fiat provenance and stale/disabled state on prices and websocket conversions (BE-DEF-012); no upstream onion default.
  • Query Studio read-only grammar + owned MySQL engine + owner usage ledger (BE-DEF-001); deployment control through a signed owned adapter with a journal-derived rollback target, plus scripts/universe/deployment-control-adapter.mjs (BE-DEF-002).
  • Network Observatory from the owned relay and template collectors (BE-DEF-014); diagnosis and ordering from the owned mempool and recorded templates (BE-DEF-016); accelerator provider directory and signed receipt verification (BE-DEF-017).
  • Private Tor/I2P relay with a durable submission table, leased worker, owner-token readback and pre-relay abort (BE-DEF-015).
  • AssumeUTXO: trusted catalogue, streaming snapshot verification with Core's hash_serialized_3 (proved against a Core 28 regtest dump), measured plans, authorized durable operator jobs (BE-DEF-018/019/020).
  • Schema 110 to 112 (additive).

Frontend

  • Registry retry/error recovery, evidence-based pulse and protocol availability, network labels, propagation partition (FE-D01 to D06); consumers of the new bootstrap, observatory, Query Studio, accelerator and relay contracts.

Repository and controls

  • Origin gate rewritten with WHATWG host parsing, an approved-origin policy and tests (RC-ORIGIN-001); external runtime defaults removed from Docker/production templates and the onion switch made explicit (RC-ORIGIN-002); Node 24.19.0 / Rust 1.89.0 container pins and a non-publishing container gate (RC-PACKAGE-001).
  • Protocol roster re-pinned with TAP on Doge's four dedicated reads.

Verification: backend unit suite 2025 passed (the 13 failing tests are the native-engine suites that CI builds), frontend 2241 vitest passed, lint 0 errors, all text/origin/branding gates green; the isolated Signet app on this branch passes 22 real API journeys (evidence in the handoff package).

🤖 Generated with Claude Code

bitcoinuniverseadmin and others added 28 commits September 17, 2026 05:25
…nd abort (BE-DEF-015)

Replace the unconditional throws behind the five private submission routes
with a real path: owned relay endpoints from UNIVERSE_PRIVATE_RELAY_ENDPOINTS
(SOCKS proxy plus onion/i2p submit URL, network-checked), bounded transaction
validation, a MySQL submission table (schema 110), a leased bounded-retry
worker that relays through socks-proxy-agent and tracks confirmation on the
owned node, owner-token authenticated readback and an atomic pre-relay abort
that reports abort-too-late once relay has started. Without a database the
feature reports durable-store-unavailable; no in-memory mode, no signing, no
third-party broadcaster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… adapter (BE-DEF-001, BE-DEF-002)

Query Studio parses a bounded SELECT grammar, renders parameterised
statements against an allowlist of explorer tables and runs them on a
dedicated read-only pool named by UNIVERSE_QUERY_ENGINE_DSN with a
deadline, row and byte limits and cursor pagination; schema discovery
reads information_schema; history is appended per API-key owner; usage
reads the in-process owner usage ledger and reports no-observations
honestly.

Restart and rollback discover the owned deployment adapter through
EXPLORER_DEPLOYMENT_CONTROL_ENDPOINT and _KEY, preview from its
capability document, drive its jobs to completion with a bounded wait,
and derive the rollback target from the adapter journal. The adapter
itself (scripts/universe/deployment-control-adapter.mjs) wraps
release.sh cutover and rollback behind HMAC-verified loopback requests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the protocol pages

FE-D01 protocol directory: every registry attempt is its own inner stream
with loading, result mapping and catchError inside the switchMap, so a
failed read no longer completes the retry subscription and Retry cancels
the attempt in flight. The 20 s budget is kept.

FE-D02 protocol directory: the view model carries the network the registry
request was addressed to, and the intro and empty-state copy name it
(Signet, Testnet, Testnet4, Regtest, mainnet) instead of "Bitcoin mainnet".

FE-D03 pulse service and strip: explicit observation state (unknown,
observed, unavailable, stale with the last sample time). The initial state
is unknown, a batch whose every item the authority refused never marks it
answering, only resolved items are counted, a generation token drops work
from a previous network or a stopped sample, and the strip shows "no sample"
instead of unmeasured zeros.

FE-D04 universe-api: getPropagationObservation$ uses backendBase like the
nodes and templates reads, keeping the encoded txid path and SSR base.

FE-D05 protocol detail: route resolution is typed (loading, registry-error,
missing, found), the registry failure is caught inside the attempt, the
error panel has a Retry control, and activity/object reads are cancelled on
every new resolution so a late answer cannot paint a previous protocol.

FE-D06 protocol detail: availability comes from the shared
protocol-availability helper (current authority state plus declared
capability), the release status is a separate qualifier, and limitation()
explains every non-available state including a stale or unreachable
authority behind a verified reader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ract

The broadcast page keeps the owner token returned once at creation in memory,
sends it on readback and abort, understands the durable relay states
(relaying, submitted, confirmed, rejected, cancelled, abort-too-late) and
reports an abort that came too late as exactly that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eplay fence, real tx status, sigop accounting, fiat provenance

BE-DEF-007/008/009: scheduleSingleTask releases its slot (identity-compared)
before running so a task can reschedule itself; runSingleTask resets the
running flag in finally and returns a typed SingleTaskOutcome. The admin
task handler verifies only a completed outcome whose persisted checkpoint
reads back complete; reindex reports scheduled only when the indexer accepted.

BE-DEF-010: track-txs never defaults to confirmed; each id is resolved as
mempool, replaced, confirmed (real block height/hash from the node), unknown
or unavailable, deduplicated and bounded.

BE-DEF-011: the JavaScript block template accumulates sigops per placed
transaction and resets per bounded block.

BE-DEF-003/004/006/013: cancellable is persisted in the run document; runs get
an initial lease and an owner token; lock conflicts leave a FAILED record;
executors heartbeat with compare-and-set while work runs; reconciliation is
batched, revalidates the lease, and returns a structured result that surfaces
storage failures. Schema 110 adds admin_adapter_runs.owner_token.

BE-DEF-005: admin nonces are claimed atomically in a shared store (MySQL
admin_nonces table, or Redis SET NX PX); no shared store fails closed; the
in-memory store is only used on the explicit EXPLORER_ADMIN_REPLAY_STORE=memory
opt-in and never evicts a live nonce.

BE-DEF-012: stored fiat rates are retained but not advertised as current when
the feed is disabled, the network is unpriced, or the observation is stale;
the prices route and websocket conversions carry the observation state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…telemetry (BE-DEF-014)

Nodes, propagation and templates come from the relay collector's mempool
poll lifecycles, the template collector's candidate templates and the
local mempool. One observer is labelled as one observer; regions, clock
offsets and inter-node latency are unknown; unobserved transactions and
empty collectors answer with explicit states.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	backend/src/api/database-migration.ts
…faults, align container pins

RC-ORIGIN-001: scripts/universe/check-origins.mjs now parses hostnames with
the WHATWG URL class, normalises case and trailing dots, rejects forbidden
providers by host equality and dot-suffix, flags infix lookalikes, and applies
an explicit runtime-origin policy: call sites and runtime configuration may
only name relative paths, loopback, container names, bitcoinuniverse.io hosts,
or the documented Liquid asset catalogue metadata exception. Build inputs are
allowed in build context only, citations in documentation and comments, and
credentials in a URL are reported without ever being printed. Source and
bundle scans are separate; the blanket production/, docker/, rust/ and
workflow exemptions are gone. scripts/universe/check-origins.test.mjs pins the
six audit fixtures, lookalikes, credentials, ports, redirects and the
metadata and citation contexts.

RC-ORIGIN-002: docker/backend/start.sh, docker/frontend/entrypoint.sh and the
production templates no longer default any endpoint to mempool.space or its
onion; the start scripts refuse a disallowed, malformed or credential-bearing
endpoint with exit 78 and name only the field and host. Fiat prices and the
mining pool refresh are off unless configured. The frontend's onion switch is
now the explicit ONION_SERVICES_API field instead of a hostname-based provider
change. scripts/universe/docker-runtime-defaults.test.mjs renders both start
scripts and proves the minimal, owned and rejected cases. Docs list the owned
endpoint prerequisites and the exact scope of the metadata exception.

RC-PACKAGE-001: the container images pin Node 24.19.0 / npm 11.17.0 and Rust
1.89.0 (verified at build time), install from the lock file, and use the
fork's production frontend build. .github/workflows/docker.yml is a
build-only gate for develop/main pull requests touching the templates, with
publication only from an explicit dispatch; workflow-safety.test.mjs pins
that shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The origin gate now refuses the inherited mempool.space onion as a backend
default; the field starts empty and the deny fixture in the relay test is
marked as such.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sured plans and authorized operator jobs

BE-DEF-018: a signed catalogue (UNIVERSE_BOOTSTRAP_SNAPSHOT_CATALOGUE) with
ed25519 producer keys and operator-pinned Bitcoin Core commitments backs
listSnapshots/getSnapshot; verifySnapshot streams the actual bytes from an
allowlisted source under a size and deadline bound, checks sha256, manifest
signature, network magic, base block and height against the owned node, and
reproduces Core's hash_serialized_3 while streaming (proved against a real
Core 28.0 regtest dumptxoutset fixture). Runs persist in
universe_bootstrap_verifications; a missing run is 404, a missing store 503.

BE-DEF-019: createBootstrapPlan uses the measured node capabilities and
chainstate, a statfs or operator-measured capacity, and a verified pinned
snapshot compatible with the node's exact release; it rejects unsupported
network/version, missing capability, unmeasured or stale capacity,
insufficient space, unverified snapshots and ineligible chainstates.

BE-DEF-020: operator POST routes sit behind the admin adapter guard (private
origin plus signed request) and loads need signed elevation and a typed
confirmation; jobs are durable (universe_bootstrap_jobs) with idempotency
keys and leases, executed by a bounded worker that issues dumptxoutset or
loadtxoutset with a configured path only and records Core's own result and
the observed chainstate; a timeout or a crash after the RPC ends in
needs-review, never a re-issued RPC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…6-go

# Conflicts:
#	backend/src/api/database-migration.ts
…ipt trust (BE-DEF-016, BE-DEF-017)

diagnoseTransaction reads the requested transaction's own mempool entry
and the owned policy floor; unknown transactions are a typed 404. The
ordering methods compare each mined block's order with the templates
recorded for its height and the relay first-seen observations, with
no-template-observed as an explicit coverage state. Accelerator
providers come from the owned directory named by
UNIVERSE_ACCELERATOR_PROVIDER_DIRECTORY and receipts verify against its
keys (ed25519 or BIP-340 Schnorr) over a documented deterministic
payload, with replay detection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ure the raw body

The operator prefix mounts the raw-body parser before the general JSON
parser so the signed request can be verified, the guard is given the same
shared replay store as the admin adapter, and the public job readback no
longer carries the operator key id or the host path of the written snapshot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	backend/src/api/intelligence/private-submission/private-submission.service.ts
#	backend/src/api/intelligence/private-submission/private-submission.test.ts
#	backend/src/api/intelligence/private-submission/submission-input.test.ts
…reads

Pinned from the regenerated backend-apis manifest: tap_doge gains chain-list,
chain-detail, holders and events descriptors, collection rows carry their
pagination style and detail routes use the controllers' real parameter names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…io and submission contracts truthfully

Bootstrap: the planner renders the backend's measured feasibility plan
(node facts, capacity, required bytes and headroom, selected verified
snapshot, assumptions, download estimates labelled as assumptions,
expected transitions, actions not executed, rollback) and its typed
rejection stages, instead of gating on sync hours the backend never
measures. The verifier starts a run, polls the 202 record until a final
state with a bounded readback that is cancelled on edit, network switch
and destroy, renders every independent check with expected/observed and
reason, shows caller inputs as inputs, and keeps 404 apart from 503. The
overview renders the four subfeature statuses with reasons; snapshot
list and detail render the catalogue fields, verification state and the
signed manifest link.

Network observatory: models follow the single-observer contract; the
page labels the one observer as such, unknown regions and unmeasured
latency as unmeasured, renders propagation and template states
(observed, not-observed, no-observations, no-templates-observed) without
a fabricated timeline, and shows a 503 owned-node-unavailable as an
unavailable state with a retry that cancels the in-flight attempt.

Query Studio: typed models for schema discovery (tables, missing tables,
grammar, source) and execution (exact big-number strings, truncated,
opaque cursor); rejected-by-grammar with position, unavailable engine
with reason, and deadline are rendered as distinct failures; a next page
is fetched through the cursor. Developer usage renders observed counts,
quota, latency and coverage or the no-observations state, and a failed
read as unavailable rather than zero.

Private submission: providers from the owned directory (identity, keys
and validity windows, terms, unmeasured health, null expiry) validate
and render; receipt verification renders each stage distinctly from its
200/409/503/400 answer; diagnosis renders the transaction's own mempool
facts or transaction-not-in-mempool; ordering renders the typed states,
the findings envelope with no-blocks-observed, and 404s as not observed
rather than empty success; the overview renders relay, queue and worker
facts.

Docs: track-txs documents the per-transaction status field and that
confirmed is only true alongside blockHeight and blockHash.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed network

Audit WP-PO-01 step 4 / frontend targeted task 4. The frontend hard-coded
network=mainnet on every Dogecoin and Zcash read, so the overlay's new
per-chain network scopes could not be reached and the Bitcoin selector was
the only network context on the page.

- UNIVERSE_CHAIN_NETWORKS, a new optional frontend env key: a JSON object
  (or JSON string, the Docker form) naming the network each non-Bitcoin
  chain is read from, e.g. {"dogecoin":"testnet"}. Default {} keeps every
  chain on mainnet. Values other than mainnet|testnet|regtest, a bitcoin
  entry, or unparseable input are ignored with one console warning and
  that chain reads mainnet. Read in chain-network.ts from
  StateService.env, memoized on the raw value.
- UniverseApiService.chainNetwork(chain) / chainNetwork$(chain): Bitcoin
  follows the selector, every other chain its configured network. Used by
  every getChain* route, protocol list/detail/section, search, sources,
  status, the scoped universe routes, the live websocket subscription and
  envelope check, bookmarks, the picker labels and the chain docs page.
- getChains$ reads /api/v1/chains/<chain>?network=<resolved> per chain,
  since the overlay lists /api/v1/chains by one network and a Signet
  reader with Dogecoin on testnet has no single list. A record answering
  another network is rejected; a typed unavailable record for the
  configured scope passes through under its own network.
- A 503 <chain>-network-unavailable body renders a message naming the
  configured network; nothing falls back to mainnet.
- generate-config.js emits object settings as JSON and quotes them in the
  Docker template; the frontend entrypoint defaults the variable to {}.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… was born from

A restart or rollback replaces the backend that drives it, so the original
executor never sees the adapter job finish. On mount the new process reads
every RUNNING deployment run that recorded its job id, reads the job back
from the adapter and closes the run with the same evidence, instead of
leaving it for the reconciler to mark abandoned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fore closing

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The backend reconnects to its database, runs migrations and reloads the
mempool before it listens again, which took 8 to 10 s on the validation
stack; the 5 s window answered 502 twice per restart. Eleven seconds stays
inside the 12 s bound the gateway tests hold a dead upstream to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ndbox

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BACKEND=none is refused by this fork because every address lookup would fail
while the site still offers them; the compose template now names an owned
Esplora on the container loopback (socket or published port) and documents
the topology. The non-publishing container gate exercises exactly this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bitcoinuniverseadmin
bitcoinuniverseadmin merged commit dadcfb7 into develop Sep 17, 2026
11 checks passed
@bitcoinuniverseadmin
bitcoinuniverseadmin deleted the fix/audit-20260916-go branch September 17, 2026 09:28
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