fix/hetzner-dns-docs
106 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7ea7328eed |
shared: live PostAdvert + delegate UPLC eval to Ogmios
Two production-essential fixes that surfaced during the path-A TTS deployment to Vector testnet: 1. localUPLCEval: false on every Plutus V3 spend ────────────────────────────────────────────── lucid-evolution 0.4.30's CML WASM UPLC evaluator has a false-rejection bug for our V3 spend pattern: it rejects perfectly valid txs with "failed script execution Spend[0] the validator crashed / exited prematurely" while the chain itself accepts them without complaint. Routing eval to Ogmios's evaluateTransaction (cardano-node's evaluator) sidesteps the WASM bug and returns correct ex-units. OgmiosLucidProvider.evaluateTx already implements this path. This also unblocks the 5 tx-submit-live tests previously skipped with the comment "UPLC wasm crashes" (M1-F-D-defer / task #28). Without this fix, every Claim/Submit/Accept tx from our supplier was failing locally before reaching the chain. The third-party summitstak.ing supplier worked because it uses a different toolchain that does not run client-side phase-2 evaluation. 2. AdvertDatum BigInt coercion ──────────────────────────── cbor-x emits float64 (`fb` prefix) for JS Number values that can't fit CBOR's compact int forms (e.g. POSIX-ms timestamps). Plutus Data has no float type, so CML.PlutusData.from_cbor_hex panics with "RuntimeError: unreachable" when it sees one. Coerce max_output_tokens, max_processing_ms, advertised_at to BigInt before encoding so cbor-x always emits major-type-0/1 integers. Mirrors the intAsBig pattern already used in EscrowDatum. 3. Live-CBOR PostAdvert builder ───────────────────────────── M1-F-4 deferred postAdvert/updateAdvert/retireAdvert/reclaim/ release on the live path. With (1) and (2) in place, posting an advert via tx:post-advert against Vector testnet now works. Added buildLiveTxForAdvert in liveCbor.ts (mirrors PostEscrow) and routed buildPostAdvertTx through it when chain is live. 40 advert/post-advert tests still pass. Verified end-to-end on Vector testnet: TTS advert posted at 5df793ac12dc426fae7a7a01d56d3e923d3985103d62f0182d9ba63dda871f96#0, full chat + TTS marketplace lifecycle (PostEscrow → Claim → Submit → Accept) confirmed completing successfully on chain. |
||
|
|
34a52d5d16 |
buyer: TTS marketplace lifecycle (audio.synthesize.piper.v1)
Path-A buyer side: PostEscrow → supplier /v1/audio/synthesize → 202+poll
→ on-chain Submit → receipt verify, returning the audio bytes + signed
receipt to the SPA. Pairs with the supplier-side scaffolding in
|
||
|
|
3c3e009a67 |
supplier: TTS capability scaffolding (audio.synthesize.piper.v1)
Path-A foundation: extend the supplier service so the same on-chain
escrow lifecycle can serve a PiperTTS supplier alongside the existing
Ollama-backed chat one. Capability dispatch is boot-time; one process
serves one capability via a single advert.
Receipt mapping for TTS is committed inline:
- prompt_hash = sha256(canonical({text, voice, format, speed}))
- result_hash = sha256(audio_bytes)
- prompt_tokens / completion_tokens = 0 (off-chain only; Piper has no
token semantics, on-chain validator never reads these fields)
New / changed files
- supplier/src/piper.ts (new): mirror of ollama.ts. callPiper(...)
POSTs OpenAI-shape /v1/audio/speech, returns {audio: Uint8Array,
contentType, wallclock_ms}. Typed errors: piper_failure | piper_
timeout | piper_malformed (with a 64-byte minimum to catch upstream
"200 OK, body length 0" cold-start)
- supplier/src/config.ts: + CapabilityKind = "chat" | "tts", + piperUrl
/ piperTimeoutMs. CAPABILITY_KIND env defaults to "chat" so existing
chat suppliers keep booting unchanged
- supplier/src/jobs.ts: JobResponsePayload becomes a discriminated
union (ChatJobResponsePayload | TtsJobResponsePayload). TTS variant
carries audio_b64 + content_type + byte_length so the in-memory
store stays JSON-clean and the GET poll route returns audio without
binary content-type negotiation
- supplier/src/jobRunner.ts: + runTtsJob (parallels runChatJob with
same 7-step shape: setRunning -> upstream -> receipt -> buildSubmit
-> awaitTx -> complete -> finally release)
- supplier/src/server.ts: + makeTtsHandler (POST /v1/audio/synthesize)
+ makeGetTtsJobHandler (poll). createApp dispatches by config.
capabilityKind: TTS supplier exposes the audio routes only, chat
supplier exposes the chat routes only. The on-chain capability_id
check on every request is belt-and-braces
Tests
- tests/unit/supplier-piper.test.ts: 9 tests (request shape, success,
network throw, AbortError -> piper_timeout, too-small body ->
piper_malformed, missing content-type fallback)
- tests/unit/supplier-routes-tts.test.ts: 9 tests (capability-routed
mounting, all 400 validation paths, sanity that the route is reachable)
- tests/fixtures/supplier-side/sample-config.ts: + buildSampleTtsConfig
243 supplier tests passing; chat path behaviourally untouched.
|
||
|
|
3152b01c04 |
buyer: indexer-driven /tasks page, path-B TTS demo, server-side error logging
Three buyer-app changes that all landed during the same browser-debug
session and were verified end-to-end on Vector testnet:
UX-3-history — /tasks page is now indexer-driven (M1-F-5 followup)
- TaskHistory.tsx fetches /v1/indexer/escrows?buyer=<pkh> from the
buyer-app proxy, groups rows by (posted_at, supplier_pkh) into
lifecycle cards, and renders newest-first with state pill,
capability/model/price, and the tx-hash chain
- main.tsx drops the LocalStorageTaskHistoryStore (chain is the source
of truth; SDK uses MemoryTaskHistoryStore for in-flight only)
Path-B PiperTTS demo — capability-routed UI without escrow yet
- buyer/src/server.ts: POST /v1/synth-speech proxies to the openedai-
speech-min host; validates {text, voice, format, speed} and returns
audio bytes with the upstream Content-Type
- buyer/src/config.ts: TTS_PIPER_BASE_URL env (optional; absence keeps
/v1/synth-speech disabled with a clean 503)
- buyer/src/ui/components/PiperTTSForm.tsx: capability-specific form
(text + voice + format + speed slider) with inline <audio controls>
and a Download .mp3 anchor; blob URLs revoked on each submit + unmount
- buyer/src/ui/pages/Dashboard.tsx: capability registry — switch on
selected.capability_id to PromptForm vs PiperTTSForm; appends a
synthetic "PiperTTS (apex-simulation)" demo card so the capability
routing is exercisable before the on-chain TTS supplier ships
- deploy/testnet/docker-compose.buyer.yml: TTS_PIPER_BASE_URL points
at https://tts-piper.vector.testnet.apexfusion.org
Server-side error logging on /v1/submit-prompt
- buyer/src/server.ts: catch block now logs reason + message + stack
(the demo session lost ~30 minutes to a "<!DOCTYPE..." JSON-parse
error caused by Cloudflare replacing origin's clean 502+JSON with
its own HTML page; the actual upstream cause was a "wallet must
hold a pure-ADA UTxO >=5 ADA" lucid-evolution error that was
invisible because the Express handler had no logger)
Tests: 10 new for /v1/synth-speech (validation, defaults, success
shape, upstream 5xx, fetch-throw); existing buyer-ui-smoke supplier
count expectation updated for the synthetic Piper card (N+1)
|
||
|
|
3f3bc72680 |
deploy: add apex-simulation host stack (Traefik + PiperTTS + voice extras)
Stand up a second deploy target on apex-simulation dedicated to "simulation"-style supplier capabilities, starting with CPU-only PiperTTS served via openedai-speech-min behind Traefik on :80. - deploy/simulation/docker-compose.traefik.yml: Traefik latest with introspection API, settles on Docker daemon 29.x API negotiation - deploy/simulation/docker-compose.tts-piper.yml: openedai-speech-min with curl-based healthcheck (image lacks wget, which Docker would have marked unhealthy and Traefik would filter from the docker provider) - deploy/simulation/deploy.sh: rsync-based deploy mirroring the apex-dashboard pattern; selective --traefik-only / --tts-only flags - deploy/simulation/install-voice-extras.sh + voice-mappings.py: idempotent post-deploy step that downloads en_US-lessac-high voice files into the bind-mount and registers a "lessac" alias in voice_to_speaker.yaml; survives a host wipe without manual steps - deploy/simulation/README.md: full host runbook - .gitignore: exclude data/ recursively, *.log, .vitest-cache, .DS_Store, __pycache__/, *.pyc, docs/*.zip |
||
|
|
cbab81efbd |
M1-F: end-to-end LLM marketplace lifecycle on Vector testnet
Bonded-escrow marketplace for prompt → response between buyers and
local-LLM suppliers, settling on Vector (UTxO) via Aiken Plutus V3
validators. Lifecycle PostEscrow → Claim → Submit → Accept proven on
Vector testnet 2026-05-06; AcceptEscrow tx 125e3cfe…7dc100d distributes
payment + bonds per the validator's distribution rule.
Workspace layout (pnpm + TypeScript):
contracts/marketplace/ Aiken validators (advert + escrow) + plutus.json
packages/shared/ Tx-builders (lucid-evolution), CBOR codecs,
chain providers (mock / read-only / live Ogmios)
supplier/ OpenAI-compatible HTTP node (Ollama by default;
vLLM / llama.cpp / OpenRouter / OpenClaw all
swap-in via OLLAMA_URL); 202+poll chat handler
buyer/ Express + React SPA; server-side runs the full
chain lifecycle via /v1/submit-prompt and
/v1/accept; SPA never holds the buyer key
indexer/ Chain-follower (extends apex-dashboard pattern),
SSE event stream + REST queries
indexer-ui/ Operator dashboard (events + suppliers + escrow
lookup)
tests/ 1098 vitest unit + integration tests (10 skipped
under M1-F-D-defer for UPLC test plumbing)
deploy/testnet/ Per-service docker-compose for the Hetzner stack
docs/EXECUTIVE_SUMMARY.* Marp deck (PDF + PPTX) — 11 slides, mermaid
diagrams pre-rendered for portable export
docs/CLOUDFLARE_WAF_BYPASS.md CF Skip-rule runbook for /v1/* paths
docs/ARCHITECTURE.md canonical spec
Live demo (testnet, 2026-05-06):
https://mp-buyers.vector.testnet.apexfusion.org (buyer SPA)
https://mp-suppliers.vector.testnet.apexfusion.org (supplier API)
https://mp-indexer.vector.testnet.apexfusion.org (indexer + UI)
|