Commit Graph

16 Commits

Author SHA1 Message Date
david
5cae0c4f6c shared: opt-in CIP-33 reference-scripts for script-spending txs
When ESCROW_REF_UTXO / ADVERT_REF_UTXO env vars are set (format
<txhash>#<idx>), Claim/Submit/Accept/Reclaim/RetireAdvert call
.readFrom([refUtxo]) instead of inlining the validator CBOR via
.attach.SpendingValidator(). Lucid-evolution 0.4.30 auto-extracts
utxo.scriptRef into the script-witness map (dist/index.js:1903–1913),
so no separate attach call is needed. Default (env unset) is unchanged.

Adds buildLiveTxForPublishReferenceScripts (a one-shot tx that
publishes both validators as scriptRef-bearing UTxOs at a caller-
supplied address — typically the all-zero-pkh enterprise burn address)
and the supplier tx:publish-reference-scripts CLI that wraps it.

Targeted at testnet trial; mainnet rollout is a separate flip after
verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:25:59 +02:00
david
15693c8532 deploy/mainnet/indexer: stop pinning OGMIOS_URL in compose
Removing the hardcoded OGMIOS_URL=ws://ogmios:1337 (apex-dashboard-mainnet
internal hostname) lets indexer/.env supply the real value — hosted WSS for
deployments that don't run a co-located ogmios container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:10:01 +02:00
david
bb97b1d09d deploy/mainnet: rename traefik network dashboard_default → traefik-net
The legacy dashboard_default name leaked from apex-dashboard on the
testnet host. On a fresh mainnet box with no apex-dashboard, traefik-net
is more accurate. Mainnet-only — testnet compose files keep
dashboard_default since they depend on the existing apex-dashboard host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:34:49 +02:00
david
dcab0ba5b5 deploy: mainnet bring-up patches (env-override genesis + host/network)
shared: VECTOR_ZERO_TIME now reads VECTOR_ZERO_TIME_MS from env, defaults
to testnet genesis (1_752_057_484_000 ms). Mainnet deploys set
VECTOR_ZERO_TIME_MS=1756485600000. Existing testnet deployments unaffected
since env unset → testnet default. Patched in both lucidContext.ts (lucid
slot config override) and liveCbor.ts buildLiveTxForSubmit (submitted_at
slot-alignment).

deploy/mainnet: buyer host changed mp.vector.* → marketplace.vector.* per
operator-owned DNS. Indexer compose drops apex-net network (we point at
the hosted mainnet ogmios over public WSS, not via apex-dashboard-mainnet
which is not deployed on this box).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:32:26 +02:00
david
8249fd3c67 buyer: marketplace-buyer-pair adversarial + happy-path runners (testnet)
run-agent-a.ts: drives Marketplace.submitPrompt + runAccept for 3 cycles
against the live supplier. Logs every tx hash, HTTP code, receipt
verification step. Verified 2026-05-12 on Vector L2 testnet: 4 cycles
clean, 28 AP3X net, supplier wallclock 19-53s (cold/hot).

run-agent-b.ts: adversarial buyer orchestrator covering 14 sub-attacks
across A (within-protocol cheating), B (protocol fuzz), C (liveness/DoS),
D (social/spec). Hard caps enforced inline (10 escrows / 25 AP3X burn /
100 reqs at <=2 rps / stop-on-first-GAP). Post-run reclaim sweep recovers
any Open/Claimed escrows past deliver_by. Verified 2026-05-13: 11 of 14
attacks BLOCKED, 2 UNCLEAR (test-design faults, not defense gaps), 1
SKIPPED (builder controls deliver_by). Surfaced the Reclaim live-CBOR
funds-at-risk bug fixed in the prior commit.

reclaim-orphans.ts + cleanup-agent-b.ts: throwaway helpers that close
out leftover Open/Claimed/Submitted escrows after an Agent-B run.

prompts/marketplace-buyer-pair.md: paired spec for Agent A + Agent B
(the source for these runs).

buyer/.env is gitignored and stays out — contains the testnet ed25519
seed for the runner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:58:14 +02:00
david
c09245a507 shared: live-CBOR for deferred builders (Reclaim, RetireAdvert)
Closes 2/4 builders from the M1-F-D-defer list (postAdvert already live in
7ea7328; updateAdvert and release remain).

Reclaim:
- buildLiveTxForReclaim in liveCbor.ts (mirrors buildLiveTxForAccept):
  spends the escrow UTxO with REDEEMER_RECLAIM (Constr3), pays
  payment + buyer_bond + supplier_bond to buyer, delegates UPLC eval
  to Ogmios via localUPLCEval:false. Validity range = tipMs ± 60s.
- reclaim.ts: replace the unconditional mockSlotToWallclockMs tipMs
  computation with `isLive ? Date.now() : mockSlotToWallclockMs(tipSlot)`,
  matching accept.ts:77. Adds the live-CBOR dispatch branch.

Funds-at-risk before this fix: with the broken tipMs path, every live
Reclaim was rejected client-side ("tip < deliver_by" using a Nov-1970
tip). If a supplier never Claimed, buyer's 6-7 AP3X stayed locked
indefinitely. Verified recovery of 4 stuck escrows on Vector L2 testnet
(txs f6660e52, 769f16fd, 79970d86, 9f4db14b — 14 AP3X total recovered).

RetireAdvert:
- buildLiveTxForRetireAdvert in liveCbor.ts: spends advert UTxO with
  REDEEMER_RETIRE_ADVERT (Constr2), advert script address.
- retireAdvert.ts: live-CBOR dispatch.
- supplier CLI: retire-advert.ts + retireAdvertFlow.ts.
- supplier/package.json: tx:retire-advert script.

Indexer:
- follower/blockProcessor + worker: handle RetireAdvert events,
  transition advert row state to Retired.
- db/cache.ts: markAdvertTerminal helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:57:51 +02:00
jelisaveta-talbot
0b93aee1ec test: clarify M1-F-D-defer status + add LIVECBOR_DEBUG env
While checking whether last night's localUPLCEval: false fix unblocks
the 5 tx-submit-live tests skipped under TODO(M1-F-D-defer), found
that those tests still crash — but at a DIFFERENT spot than before.

Original UPLC issue (now fixed in production via 7ea7328 + fd24598):
- lucid's CML WASM UPLC interpreter falsely rejected our Plutus V3
  Submit/Claim/Accept txs. End-to-end chat + TTS lifecycle now works
  on Vector testnet by routing eval to Ogmios.

New / remaining test-side issue:
- Even with a proper evaluateTransaction mock response, lucid's
  applyUPLCEvalProvider → CML TransactionBuilder.set_exunits still
  panics with wasm "unreachable" inside the test fixture. The
  production path doesn't hit this (real Ogmios + real wallet UTxOs).
  Whatever the test-mock + CML interaction trips, it's separate from
  the original UPLC bug.

Refinements landed
- liveCbor.ts: opt-in LIVECBOR_DEBUG=1 env that prints the full inner
  err + WASM stack from rethrowAsTxError, so the next investigation
  doesn't need to re-add this scaffolding.
- tx-submit-live.test.ts:
  - setupSubmitHappyPath() now mocks "evaluateTransaction" with a
    proper EvalRedeemer-shaped response (useful baseline for any
    future un-skip attempt; not enough on its own to make the tests
    pass).
  - The first skipped test's TODO comment updated to record what's
    fixed in production vs. what remains as a test-infra bug.

Test counts unchanged (3 pass, 5 still skipped); production path
still verified end-to-end on testnet. Task M1-F-D-defer stays pending
but the original UPLC-interpreter blocker is gone.
2026-05-11 09:09:49 +02:00
jelisaveta-talbot
1f1a7d5a59 buyer: response archive (SQLite + filesystem) for off-chain audit trail
The on-chain Submit tx anchors sha256(canonicalize(receipt)) so the
receipt's signature is provably tied to a specific Cardano block — but
without persisting the receipt content + the actual response bytes
nowhere can buyer or supplier produce the artefacts a dispute would
need to argue about. This commit closes that gap on the buyer side.

What's new

- buyer/src/db/schema.ts (new):
  `responses` table keyed by escrow_ref, indexes on completed_at /
  capability_id / supplier_pkh. Stores metadata + receipt JSON +
  Ed25519 signature; large blobs (request envelope, response bytes)
  live on disk per-escrow.

- buyer/src/db/archive.ts (new):
  ResponseArchive class — persistChat / persistTts / list / get /
  readRequest / readResponse. Filesystem layout under ARCHIVE_DIR:
    <txhash>_<index>/request.json   (always JSON)
    <txhash>_<index>/response.{ext} (txt/json for chat, mp3/wav/… for TTS)
  Synchronous (better-sqlite3 + fs.writeFileSync) — volume is low and
  the simplicity is worth more than async overhead.

- buyer/src/server.ts:
  - AppDeps.archive plumbed through createApp.
  - /v1/submit-prompt + /v1/submit-tts persist BEFORE returning to
    the SPA, using canonicalize() for chat (same bytes the supplier
    hashed; JSON.stringify produced different bytes → wrong sha256).
    Persist failures are logged but never abort the response — the
    on-chain receipt is still authoritative.
  - New read-only endpoints:
      GET /v1/responses?limit=…           list metadata + parsed receipts
      GET /v1/responses/:ref               single-row detail
      GET /v1/responses/:ref/request       request artefact (application/json)
      GET /v1/responses/:ref/response      response artefact (verbatim bytes)
  - URL path uses "<txhash>_<index>" (the dir-safe form) since "#"
    can't appear unencoded in URLs (fragment delimiter); server
    normalises back to canonical "<txhash>#<index>" for SQLite lookup.
  - 503 service_unavailable on /v1/responses* when boot has no
    archive (ARCHIVE_DIR unset or DB unopenable) — the rest of
    the buyer-app still works.

- buyer/src/ui/pages/TaskHistory.tsx:
  Joins indexer escrows (chain state) + /v1/responses (archived
  artefacts) by escrow_ref. Each completed lifecycle gets an
  archive panel showing:
  - Request panel (above, chronological order): human-readable
    summary — first user message for chat, `text + voice + format +
    speed` for TTS, raw JSON fallback otherwise.
  - Response panel: assistant text inline for chat, <audio controls>
    for TTS; Download response / Download request anchors.
  - "Verify hash" button — fetches the response bytes, computes
    sha256 via crypto.subtle, compares to receipt.response_hash;
    green ✓ on match, red ✗ with truncated computed/expected on
    mismatch.
  - Collapsible "Receipt + signature" with the full canonical
    receipt JSON + the supplier's Ed25519 hex sig.

- buyer/src/config.ts: + ARCHIVE_DIR env (default "./data/archive").

- buyer/Dockerfile: builder gains python3/make/g++ + the
  better-sqlite3 native-binding fetch step (mirrors indexer/Dockerfile);
  runtime image stays toolchain-free.

- deploy/testnet/docker-compose.buyer.yml: bind-mounts
  ../../data/buyer/archive (repo-root-relative, not file-relative)
  into /data/archive. ARCHIVE_DIR env points at that path.

Tests

- tests/unit/buyer-archive.test.ts (new, 14 tests): persistChat /
  persistTts unit coverage (filesystem layout, INSERT-OR-REPLACE
  semantics, content-type → extension mapping for mp3/wav/opus/aac/
  flac, list ordering + limit, single-row get/read miss-vs-hit) +
  HTTP integration (503 when disabled, happy paths returning
  metadata / request / response artefact bytes with correct
  content-types, 404 on unknown ref).
- tests/unit/buyer-ui-smoke.test.tsx: TaskHistory fetch mock now
  also handles /v1/responses (returns empty list — same code path as
  "archive disabled" production case).

45 buyer tests passing; chat + TTS lifecycle verified end-to-end on
Vector testnet; "Verify hash" button confirms sha256(response bytes)
matches receipt.response_hash for both capabilities.
2026-05-11 08:49:07 +02:00
jelisaveta-talbot
fd24598186 shared: also delegate UPLC eval on Accept tx
buildLiveTxForAccept has its own .complete() call separate from
buildSpendOpenOrClaimedTx (which already routed eval to Ogmios in
7ea7328). The same lucid 0.4.30 CML WASM false-rejection bit Accept
on every escrow — confirmed by 502s on three different pending
receipts in the SPA after the chat/TTS Submit fix landed.

Adding `localUPLCEval: false` to Accept's .complete() options closes
the gap; verified end-to-end against Vector testnet (Accept tx
0385442fd7ef72743194f8fa0b468c2c56bacb90b023f86ec2c3df22b18b7ca8
confirmed under the prior workaround; subsequent Accepts on the
patched build also confirm cleanly).
2026-05-07 21:48:47 +02:00
jelisaveta-talbot
cc3de1a5f4 deploy: marketplace-supplier-tts compose for audio.synthesize.piper.v1
Second supplier instance on apex-dashboard, dedicated to the new
audio.synthesize.piper.v1 capability. Reuses supplier/Dockerfile but
sets CAPABILITY_KIND=tts so the boot dispatch mounts /v1/audio/
synthesize routes instead of /v1/chat/completions.

PIPER_URL points at the public CF hostname for the openedai-speech-min
host on apex-simulation (no shared docker network with this host).

Wallet, advert UTxO, .env.tts, and Cloudflare DNS+Skip rule are
managed out-of-band; this compose just brings up the container that
serves the marketplace-side of the TTS lifecycle.
2026-05-07 05:17:20 +02:00
jelisaveta-talbot
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.
2026-05-07 05:17:19 +02:00
jelisaveta-talbot
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 3c3e009.

Files

- packages/shared/src/tx/escrow/postTtsEscrow.ts (new):
  buildPostTtsEscrowTx() mirrors postEscrow.ts but commits a TTS-shaped
  prompt_hash = sha256(canonical({text, voice, format, speed})). The
  same hash is exposed as ttsPromptHash() so the buyer SDK and supplier
  validation can pin the canonicalisation; any drift fails the
  supplier's prompt_mismatch gate. Validates body shape (text non-empty,
  voice/format in allow-list, speed ∈ [0.5,1.5]); reuses the chat live-
  cbor path which is already capability-agnostic.

- packages/shared/src/tx/index.ts: export the new TTS surface.

- buyer/src/sdk/Marketplace.ts: + submitTts() — full lifecycle mirror of
  submitPrompt with TTS-shaped POST body, /v1/audio/synthesize endpoint,
  and audio_b64 in the polled response. Receipt verification gates on
  prompt_hash via ttsPromptHash() instead of canonicalize(messages).

- buyer/src/sdk/types.ts: + SubmitTtsOptions / SubmitTtsResult.

- buyer/src/server.ts: + POST /v1/submit-tts (mirrors /v1/submit-prompt
  shape; routes to marketplace.submitTts).

- buyer/src/ui/components/PiperTTSForm.tsx: two-mode rendering. When
  advertRef + payment_lovelace are passed, posts to /v1/submit-tts and
  decodes base64 audio into a blob URL. When neither is passed, falls
  back to /v1/synth-speech (the path-B direct demo proxy).

- buyer/src/ui/pages/Dashboard.tsx: capability dispatch routes a real
  on-chain TTS supplier to the marketplace mode and the synthetic demo
  card to the direct mode (its utxo_ref="demo:piper-tts" fails the ref
  parser → advertRef is null → demo mode). Same component for both.

Tests still passing: 31 buyer + 29 supplier; both typechecks clean.
Chat path behaviourally untouched.
2026-05-06 22:00:31 +02:00
jelisaveta-talbot
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.
2026-05-06 19:01:35 +02:00
jelisaveta-talbot
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)
2026-05-06 19:00:40 +02:00
jelisaveta-talbot
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
2026-05-06 18:59:20 +02:00
jelisaveta-talbot
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)
2026-05-06 13:27:48 +02:00