Skip to content

chore(ci): remove obsolete self-hosted Supabase health-gate - #15

Open
yamancan wants to merge 413 commits into
mainfrom
chore/remove-selfhosted-healthgate
Open

chore(ci): remove obsolete self-hosted Supabase health-gate#15
yamancan wants to merge 413 commits into
mainfrom
chore/remove-selfhosted-healthgate

Conversation

@yamancan

Copy link
Copy Markdown
Contributor

Ne

.github/workflows/deploy-webapp-vps.yml (self-hosted Supabase deploy health-gate) kaldırıldı.

Neden

trionlabs8004 Supabase stack'i self-hosted Dokploy docker-compose'dan Supabase Cloud'a taşındı (2026-07-17) ve self-hosted stack decommission edildi.

Bu workflow, push edilen commit'i Dokploy'un self-hosted compose stack'ine deploy etmesini bekleyip ardından artık var olmayan container'ları (trionlabs8004-migrate / -db / -edge-functions) health-check ediyordu. Şu path'lerden birine push olduğunda 15 dk hiç gerçekleşmeyecek bir deploy'u bekleyip fail ediyordu:

  • webapp/supabase/migrations/**, webapp/supabase/functions/**
  • webapp/packages/indexer/**, webapp/packages/sdk/src/core/config.ts
  • webapp/docker/docker-compose.supabase.yml

Etki

  • Cloud migration/function'lar artık supabase db push / supabase functions deploy ile (migration-managed repo) gidiyor.
  • Frontend hâlâ Dokploy git-watch (synth) ile deploy oluyor — bu değişmedi.
  • Bu health-gate'in koruduğu deploy yolu artık yok → kaldırmak CI'ı düzeltiyor.

Sonraki (opsiyonel, ayrı PR)

İstenirse cloud tarafı için yeni bir health-gate eklenebilir: push'ta supabase db push + Supabase Management API ile migration/edge/indexer-lag doğrulaması (cloud creds GH secrets'a eklenerek).

🤖 Generated with Claude Code

ozthewizard0 and others added 30 commits April 7, 2026 11:25
git-subtree-dir: webapp
git-subtree-mainline: a1ebd8f
git-subtree-split: 4f9e1a0
claude and others added 27 commits June 3, 2026 02:35
Addresses the one real issue from adversarial review: a non-transient
foreign-key violation (e.g. a genuinely dropped/unparseable parent Registered
event) made the batch defer forever, silently stalling the whole reputation or
validation stream behind one bad event.

- Track consecutive defers per contract in indexer_state.defer_attempts
  (migration 039); reset to 0 on a completed scan, increment when a batch is
  left un-advanced.
- After MAX_DEFER_ATTEMPTS (5) consecutive defers, treat the failure as
  permanent: log at error level (so monitoring can surface the dropped event)
  and skip it so the checkpoint can advance and unblock the stream.

Adds getDeferAttempts and a defer_attempts arg to updateCheckpoint, plus a test
for the escape-hatch path. Regenerated the edge copy.

https://claude.ai/code/session_018NmC15oDtEFsBE7tUzDW1B
- Add a toText() helper: scValToNative returns a Uint8Array for non-UTF-8
  Soroban String fields, and String(uint8array) yields a comma-joined byte
  list ("104,105") rather than text. Route String/Symbol/URI/tag fields
  through toText so non-UTF-8 bytes decode correctly; also replaces the
  scattered String(data.x ?? '') pattern.
- Drop the redundant throw in the validation_response parser (it was caught by
  the wrapper and returned null anyway, identical to the out-of-range path).
- Correct the scValToNative comment in reputation.ts (u64/i128 always return
  bigint; only u32/i32 return number).

Verified against the emitting Rust contracts: event names, topic ordering,
and value ranges match; the metadata_set body is always an ScMap (single-field
contractevent default), so bytesToUtf8(data.value) is never passed undefined.

https://claude.ai/code/session_018NmC15oDtEFsBE7tUzDW1B
… cleanup (S2 review)

- Cold-start retention clamp (HIGH): on a fresh checkpoint, startLedger was the
  deploy ledger, which ages out of the RPC retention window and makes
  getEvents fail with "start is before oldest ledger" every run (permanent
  wedge). Probe the oldest retained ledger and clamp the scan start to it;
  older events are recoverable only via the backfill script.
- Request limit:10000 (max) instead of the default 100 -> ~100x fewer
  pagination round-trips on busy contracts / cold-start catch-up. Cursor
  pagination still walks the full window, so correctness is unchanged
  (Soroban getEvents: empty page = end-of-stream, verified against the spec).
- Soft time budget: runIndexer accepts a deadline; the loop stops cleanly
  between pages/contracts and the edge function passes a 110s budget (< its
  120s hard timeout) so the lock is released and the checkpoint stays
  consistent instead of the isolate being killed mid-write.
- retry.ts: clamp the Retry-After delay by maxDelayMs (a `Retry-After: 120`
  would otherwise sleep 120s, overriding maxDelayMs and the time budget).
- Collapse the three per-contract checkpoint reads into one getCheckpointState
  round-trip.
- Remove the dead last_cursor column (migration 040): it was written every
  run but never read (resume is always from last_ledger+1). Refresh the
  generated indexer_state types (also adds the missing defer_attempts).
- Surface escape-hatch drops as result.skippedEvents for observability.

https://claude.ai/code/session_018NmC15oDtEFsBE7tUzDW1B
- Treat transient PostgreSQL SQLSTATEs as retryable, not just FK violations:
  add 55P03 (lock_not_available), 40001 (serialization_failure), 40P01
  (deadlock_detected). The lock_timeout on insert_feedback_response (migration
  026) raises 55P03 under contention "for the caller to retry" — but the
  caller dropped it as a permanent error. Now they defer and retry next run.
- Tighten release_indexer_lock (migration 041): migration 038's
  `(p_owner IS NULL OR ...)` made a NULL-owner release an unconditional delete
  that could wipe a live, differently-owned lock. Now release only deletes the
  row it owns (NULL matches NULL). Give the backfill script its own owner token
  so its lock release is fenced too.
- Switch insert_feedback_response idempotency from tx_hash to the globally
  unique Soroban event id (migration 041 + event_id column and partial unique
  index). tx_hash is nullable (NULL = NULL is never true, silently disabling
  idempotency) and not unique per event (a tx emits many events / two responses
  in one tx would collapse). The parser now carries event.id; backfill builds a
  deterministic txHash:index id.
- Refresh the stale generated RPC signatures in types.ts (p_owner, p_event_id).
- Document UriUpdated's destructive-but-replay-safe UPDATE.

https://claude.ai/code/session_018NmC15oDtEFsBE7tUzDW1B
…4 review)

- Make the drift --check airtight: it now walks the edge copy and fails on
  orphan files (a renamed/deleted source file or stale committed file would
  otherwise linger in _shared and ship to Deno while --check stayed green).
- Add `deno check` of the real edge entrypoint to CI. tsc uses Node module
  resolution and structurally cannot catch Deno-only failures (a specifier
  that resolves under Node but 404s under Deno, npm: specifiers, Deno.* APIs);
  the drift check only proves the copy matches source, not that it loads under
  Deno. Verified the check passes against the current entrypoint.
- Guard sdk-config.ts (vendored from packages/sdk) with a drift test: the
  contract addresses / deploy ledger drive which chain state the indexer reads,
  so a stale copy after a redeploy would silently index the wrong contracts.
- env.ts now reads Deno first (the edge copy runs under Deno; a partial process
  polyfill could shadow the real Deno.env value) and treats empty string as
  unset so a blank in one runtime can't mask a real value in the other.
- Generator transform also rewrites dynamic import('./x.js') specifiers, not
  just static `from` imports.
- Pin the generated edge copy to LF via .gitattributes so a CRLF checkout
  can't produce phantom drift failures; the --check now also normalizes EOL.
- Correct the docker functions-init comment: it is a local-dev regenerate that
  writes the tree, not CI's read-only --check.

https://claude.ai/code/session_018NmC15oDtEFsBE7tUzDW1B
… lock-release blip mask a successful run

The retention clamp previously guarded only cold start (lastLedger === 0).
If the indexer is stopped longer than the RPC event-retention window (as
little as ~17h), the saved checkpoint ages out: startLedger = lastLedger+1
falls before oldestLedger, every getEvents hard-fails with "start is before
oldest ledger", the checkpoint never advances, and the contract is wedged
permanently. Probe the oldest retained ledger and clamp forward on ANY start,
not just cold start, without assuming retention is long enough to make the
resume case impossible. The forced-forward jump is surfaced via the existing
gap detector; the skipped range remains recoverable through the backfill
script.

Also harden runIndexer's finally: a transient release_indexer_lock transport
failure no longer replaces a successful run's result with a throw (the lock
self-heals via acquire's 180s stale-sweep). It is logged instead.

Tests: +1 resume-after-downtime clamp case; probe-call-count and clamp-message
assertions updated for the now-unconditional probe. 67 passing, tsc clean,
edge copy regenerated and drift-checked.

https://claude.ai/code/session_018NmC15oDtEFsBE7tUzDW1B
…ll contracts

The retention-clamp probe (getOldestLedger) was awaited unguarded, so a
transient RPC failure on it threw out of runIndexerLoop and aborted the whole
run — including contracts fully in-window that needed no clamp at all. On a
flaky/rate-limited RPC that meant zero forward progress on every affected run.
Wrap the probe per-contract: on failure, log and skip the clamp for that
contract and proceed with the un-clamped start (if it really has aged out of
retention, the getEvents below fails in the existing per-contract catch and
only that contract defers), while the others still advance. A failed probe is
not memoized, so the next contract re-attempts it.

Also:
- Decouple the defer-counter decision from the run-global result.timedOut via a
  per-contract deadlineStopped flag, so a future refactor that clears/reorders
  the shared flag can't mis-count a deadline stop as a defer.
- parsers/identity.ts: route the metadata `key` topic through toText, not
  String(), so a non-UTF-8 key is decoded as text instead of being persisted as
  a comma-joined byte list ("104,105").

Tests: probe-failure-does-not-abort-run (all contracts still advance);
Retry-After clamped to maxDelayMs; toBigInt integer/type rejection. Regenerated
the edge _shared copy; drift check + tsc clean. 73 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…igrations)

The deploy SSHs into the VPS and ran `bash scripts/build-edge-function.sh`,
which (after the indexer pipeline consolidation) calls
`node sync-indexer-to-shared.js`. The VPS deploy shell has no `node` on PATH, so
the step exited 127 and the job died BEFORE applying migrations — every deploy
failed at the edge-rebuild step.

The rebuild is redundant: supabase/functions/_shared/indexer/ is committed and
CI enforces zero drift from packages/indexer/src
(`sync-indexer-to-shared.js --check`), so `git reset --hard origin/main` already
places the correct, current edge artifact. Remove the host-side rebuild so the
deploy needs no `node` and proceeds to migrations + edge restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prod has the `mpp_enabled` column but schema_migrations does not record
037_mpp_column.sql as applied (it was applied via a different path), so the
deploy's migration loop re-runs 037 and dies on `ADD COLUMN` /
`CREATE INDEX` ("column already exists"), never reaching 038-041.

Guard both with IF NOT EXISTS (the function/grant are already CREATE OR REPLACE).
Schema outcome is identical; the migration now re-runs as a no-op, gets recorded,
and the runner proceeds to the new indexer migrations. Matches the idempotent
pattern 038-041 already follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mark 037_mpp_column.sql as applied (ON CONFLICT DO NOTHING) before the migration
loop so the runner skips re-running it. Its effects (mpp_enabled column, index,
search function) are already present on prod from an earlier apply path that
never recorded it here; re-running collided ("column already exists", then
"cannot change return type"). This unblocks the genuinely-new 038-041 indexer
migrations. Reconcile line is temporary and will be removed once recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
037_mpp_column.sql is now recorded in prod's schema_migrations (the reconcile
ran and the deploy confirmed "037 - already applied"), so the temporary
reconcile INSERT is no longer needed. Future deploys skip 037 normally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- register: add "Are you an agent?" quick-add card at the top of /register
  (copyable npx skills add ... --skill 8004stellar, view-skill link, agnostic copy)
- rebrand "Claude Code Skills" -> "Agent Skills" and use agent-agnostic
  wording across intro/one/developers
- standardize skill ids on the ...stellar convention: 8004s -> 8004stellar;
  rename skills/x402s -> skills/x402stellar (frontmatter, cross-refs in both
  SKILL.md files, README, TECHNICAL, and webapp install commands)
… leaderboard hardening

Verified follow-ups from the multi-agent indexer review:

- SSRF: resolve agent_uri hostnames (A/AAAA) and block any that point at
  private/internal space (cloud metadata 169.254.169.254, internal docker
  services); also catch IPv4-mapped IPv6. Fails open on restricted runtimes.
- Index the OZ `transfer` event so agents.owner follows NFT transfers, and
  delete orphaned agent_metadata on transfer (contract's clear_all_metadata
  emits no per-key event). Applied to both the live indexer and the backfill.
- Stop silently dropping events on a non-retryable write failure: defer the
  batch (checkpoint un-advanced) so a transient blip is retried; widen the
  retryable PG-code set; wrap getLatestLedger in withRetry.
- Migration 042: widen leaderboard avg/total_score cast to numeric(40,2)
  (a single 1e20 feedback otherwise overflows numeric(10,2) and permanently
  freezes the refresh), and throttle refresh_leaderboard() with a force
  variant for the backfill's final rebuild.
- API: sanitize+cap service fields in normalizeServices; scope the
  feedback_responses query to the current page.
- Document the registry-id data-wipe step in the redeploy runbook.
The indexer wrote one PostgREST round-trip per event, so a 100-event page = 100
sequential writes and a 5000-event backfill = 5000 calls, overflowing the 120s
function budget.

A page whose events ALL parse to the same pure-upsert type — the dominant
backfill shape (long runs of NewFeedback / ValidationRequest / Registered /
MetadataSet) — now collapses into ONE multi-row upsert. Mixed pages, single
events, and the RMW types (UriUpdated, AgentWallet*, AgentTransferred,
FeedbackRevoked, ResponseAppended, ValidationResponse) keep the per-event path.

Correctness:
- Parse-once: the page is parsed a single time and feeds both the fast path and
  the per-event fallback (no double-parse; preserves parser call counts).
- The checkpoint (maxLedger) advances only on a SUCCESSFUL bulk write, so a
  deferred page is never checkpoint-skipped.
- On ANY bulk failure the page falls back to the per-event loop, preserving the
  FK-defer / skip-after-N handling; re-apply is safe (idempotent upserts, and a
  failed PostgREST statement rolls back wholesale, so nothing partial leaks).
- In-batch dedupe keeps the last row per ON CONFLICT key (Postgres forbids a
  repeated conflict key in one upsert array); last-wins mirrors per-event replay.

Tests: 89 pass (+8) — bulk mappers/dedupe (db.test.ts) and fast-path / fallback /
mixed-page integration (indexer.test.ts). Dual-runtime synced; tsc, deno check,
and sync --check all clean.
Chunk each bulk-upsert writer at BULK_CHUNK_SIZE (500) rows so a single
homogeneous event page (up to EVENTS_PAGE_LIMIT events, each up to the
on-chain 4KB metadata/URI cap) can't balloon into one multi-MB
INSERT ... ON CONFLICT that trips statement-size / memory / time limits.
Chunks upsert sequentially; a later-chunk failure throws and the indexer
falls back to the idempotent per-event path, so an already-applied earlier
chunk is harmless. Routes all four bulk writers through one upsertChunked
helper (also collapses the four near-identical upsert+assert blocks).

Addresses the sole Low finding from the multi-lens security review of the
bulk-upsert branch; the two Info items were verified non-issues (the
processed counter matches per-event semantics; the warn-log error.message
matches the file's established ops-logging pattern).
…e bind-mount (#10)

docker restart reuses the existing container, which can keep a stale
handle to the bind-mounted supabase/functions/ after the deploy's
git reset --hard swaps files — leaving edge serving deleted/old code or
wedged entirely (observed in prod outage).

Switch to docker compose up -d --force-recreate so the container is
rebuilt and the mount + loaded functions are fresh. -p pins the existing
Dokploy project (verified via container labels) so no duplicate stack is
spawned; --no-deps keeps it edge-only so the DB is never touched by a
code-only deploy. Validated with compose --dry-run against the live VPS:
targets trionlabs8004-edge-functions Recreate, nothing else.
…ransfer, honest liveness, dead-letter) (#9)

* fix(indexer): 8004 faithful-mirror hardening (ledger guards, atomic transfer, honest liveness, dead-letter)

Grounds five indexer fixes in the 8004 faithful-mirror principle (the DB must
monotonically + idempotently reflect chain truth and never silently lose data):

G4 monotonic ledger guards: UriUpdated / FeedbackRevoked / ValidationResponse
  blind UPDATEs now carry a PostgREST `.or(<col>.is.null,<col>.lte.N)` guard so a
  replayed/out-of-order older event can no longer overwrite newer mirrored state.
  Feedback stays append-only (revocation is a flag, never a delete). (migration 046)

G4 atomic transfer: AgentTransferred is one SECURITY DEFINER apply_agent_transfer
  RPC (owner change + metadata clear in a single, ledger-guarded transaction), so
  the mirror is never observed mid-transfer with the new owner but the prior
  owner's metadata attached. (migration 047)

G3 honest liveness: updateCheckpoint stamps last_advanced_at only on real forward
  progress; both health endpoints read it, so a wedged-but-still-ticking contract
  now reads stale instead of fresh. (migration 044)

G3 dead-letter: the MAX_DEFER_ATTEMPTS skip and the RPC retention clamp now record
  the dropped event/range in indexer_dead_letter (durable, replayable) instead of
  an ephemeral log; a silent drop is no longer indistinguishable from censorship.
  Best-effort write that can never re-wedge the loop. (migration 045)

G9 bulk/per-event equivalence test: pins all four bulk-eligible writers to
  byte-identical rows vs the per-event path, so the mirror is provably
  path-independent.

Also fixes a latent upsertChunked deno-check failure (TS2208/TS2345) from the
prior bulk-upsert change. Migrations 044-047 are additive and safe to apply ahead
of the code. Local: tsc clean, 102/102 vitest, sync in-sync, deno indexer+api
gates clean. Verified by a 5-agent adversarial pass (PostgREST/.or semantics,
plpgsql atomicity, merge-duplicates upsert-omit, dead-letter wiring).

* fix(backfill): canonicalize writes to match the live indexer (G8)

The backfill script is the README-mandated rebuild path after every redeploy and
the only recovery for dropped/clamped events, but it had drifted from the live
indexer in ways that corrupt the rebuilt mirror. Align it with the (now verified)
canonical writers so a backfilled mirror == a live-indexed one:

- value: i128 feedback value was Number(d.value) — lossy past 2^53 and a throw
  on a bigint via JSON.stringify. Serialize as a decimal string into the
  numeric(78,0) column, matching db.ts value.toString().
- idempotency: the feedback INSERT had no onConflict, so a re-run / retry threw
  duplicate-key. Upsert on (agent_id,client_address,feedback_index).
- validation parity: drop new_feedback with feedbackIndex < 1 or valueDecimals
  outside 0..18, matching parsers/reputation.ts.
- atomic transfer: AgentTransferred did a non-transactional PATCH + DELETE; route
  it through the same apply_agent_transfer RPC (migration 047) the live path uses.
- monotonic guards: set uri_updated_ledger / revoked_ledger / response_ledger
  (migration 046) so a later live event compares correctly after a backfill.

backfill is lock-exclusive with the live indexer and replays in ledger order, so
these make the two writers produce identical state. Script-only, reversible.

* feat(web): surface validation pillar, per-record chain links, and indexer freshness (G5/G6/G3)

Make three 8004 faithfulness properties VISIBLE in the explorer (additive UI only;
svelte-check clean):

G5 validation tab: the Validation Registry (third, independent trust axis) was
  fully indexed but invisible — the agent page had only Metadata + Reputation tabs
  and never queried the validations table. Add a read-only Validation tab querying
  validations, presenting each attestation by its VALIDATOR address (distinct
  provenance from client feedback), response score / pending, request+response URIs
  and hashes, and per-record chain links. Feedback and validation are shown as
  distinct trust signals, never blended.

G6 per-record verifiability: tx_hash/created_ledger are stored but were never
  surfaced. Add explorerAccountUrl/explorerLedgerUrl helpers; link the owner,
  identity registry, and each feedback + validation record to stellar.expert; show
  the on-chain feedback hash so a consumer can independently re-verify.

G3 freshness badge: a faithful mirror must expose its own freshness. Add a root
  layout read of indexer_state and a small "synced to ledger N" badge (amber
  "syncing…" when the laggard contract's last_advanced_at is >5min stale), pairing
  with the honest-liveness backend change. Degrades to nothing if unavailable.
Dokploy already auto-deploys this stack on push (git-watch): it pulls
main, runs docker compose up which applies migrations via the in-compose
migrate service and recreates the edge runtime. The SSH workflow was a
SECOND deploy path racing Dokploy — it git reset --hard + recreated the
edge container, then Dokploy's own deploy (~8 min poll lag) ran compose
up which swapped the bind-mounted functions/ inode under the still-running
edge container without recreating it, orphaning the mount (worker boot
error: could not find an appropriate entrypoint) and taking edge down.
Confirmed: migrate service ran on Dokploy's deploy (exit 0, migrations
applied); webapp app was rebuilt by Dokploy on the same push.

Stop deploying from CI. Instead wait for Dokploy to deploy the pushed
commit, then assert the stack came up healthy (migrate exit 0, every
migration file applied, edge not orphaned, indexer advancing, no fresh
500s), failing the run loudly if not. This is the safety net that was
missing when prod went down silently twice.
Add registry_address (NOT NULL DEFAULT = live mainnet identity registry) to
agents + agent_metadata + feedback + feedback_responses + validations, plus
network on agents, plus parallel composite UNIQUE indexes alongside the
existing PKs/uniques.

Purely additive: no PK/FK/unique-constraint change, no read-query or
leaderboard change. The constant DEFAULT backfills every existing row and
covers indexer writes that do not yet supply the column, so the current
indexer and all reads keep working unchanged. The composite indexes exist so
Phase B can switch onConflict targets to them with no breakage window.

Phase A of 5 toward composite (registry_address, id) keying so an
identity-registry redeploy no longer requires TRUNCATEing reputation history
(README 'After redeploying'). A-C are non-destructive; D promotes the
composite PK and drops legacy constraints.

Staged against a fresh prod schema dump on local postgres 17: clean apply +
idempotent re-apply + post-state verified (cols, 5 unique indexes, DEFAULT
backfill).
normalizeRecord() camelCased every key as it walked the response tree,
including the opaque user-controlled `metadata` map. A key like
`social_links` came back as `socialLinks`, so SDK reads could not
faithfully reproduce on-chain state. Pass the `metadata` value through
verbatim and add a round-trip regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rate limiter failed OPEN: a non-inet IP key ('unknown', a spoofed/empty
X-Forwarded-For) made check_rate_limit's `p_ip::inet` cast throw, which
the caller swallowed and allowed the request. Now isPlausibleIp() funnels
bad keys to a shared '0.0.0.0' sentinel, and migration 049 wraps the cast
in a BEGIN/EXCEPTION (also fail-closed) and revokes the function's EXECUTE
grant from anon/authenticated/PUBLIC (it was an unauthenticated INSERT
primitive), granting only service_role.

minScore was filtered per-page in JS, so the reported total and hasMore
were wrong and pages could under-return. Resolve qualifying agent ids from
leaderboard_scores (bounded, highest-scored first) and constrain both the
count and data queries at the DB, keeping pagination consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- mpp-client: drive the RPC from stellarConfig.rpcUrl instead of the
  hardcoded 'soroban-mainnet.stellar.org', which is not a usable public
  endpoint and broke every live mainnet MPP charge. The network-mismatch
  guard above guarantees the configured RPC matches the challenge network.
- mpp-client: apply the computed validUntilLedger to the sponsored auth
  entry before signing (previously dead code), bounding the signature to
  the challenge expiry; drop the now-unused authorizeEntry import.
- x402: add the primary network guard at the call site
  (TryAgentPanel checks paymentRequired.accepts[].network before signing),
  with a defense-in-depth guard in the signer for parity with MPP.
- api proxy: positive-allowlist the endpoint and reject any '.'/'..'
  path segment so a crafted path can't traverse to another edge function.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e hatch

A transient getEvents failure set scanCompleted=false, which incremented
defer_attempts identically to a genuine write defer. Five consecutive RPC
blips could therefore trip the skip-after-N escape hatch and drop a real
event on the next run — undermining the "never silently drops" guarantee.

Track fetch failures separately (fetchFailed) and preserve the defer
counter on a fetch failure (like a deadline stop) instead of incrementing;
only a retryable WRITE defer increments it. Update the existing test to the
corrected expectation and add a regression test. Edge copy re-synced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reproducible-build claim (byte-identical mainnet/testnet WASMs) was
never verified by CI. Add a committed digest manifest (contracts/wasm.sha256)
and a `make verify-wasm` target, gate it in CI, verify Cargo.lock is up to
date before the build (cargo metadata --locked), assert the build does not
mutate the lock, and run `make test` with --locked. Also add a
least-privilege `permissions: contents: read` block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shipped 8004stellar skill misstated on-chain behavior that would lead
integrators to ship broken or wrong-trust code:
- append_response is permissionless (any authenticated caller; responder is
  recorded in the event), not agent-owner-only.
- validation response is a 0-100 score (values >100 revert), not 0/1.
- generateRequestNonce is async and must be awaited.
- Identity/Reputation/Validation error-code lists now match errors.rs.
The x402 skill's stellar-sdk compatible-version range is made consistent.
README notes the agent identifier's `{network}` uses friendly labels
(`mainnet`) rather than the CAIP-2 chain id (`stellar:pubnet`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The allowlist guard rejected only literal '..'/'.'/''  segments, but
fetch()'s WHATWG URL parser percent-decodes each segment once before
normalizing. So `agents/%2e%2e/%2e%2e/indexer` passed the raw check yet
collapsed to `.../api/indexer`, escaping the api/v1 namespace to reach a
different edge function on the same gateway.

Decode each segment once (matching that single decode), reject dot-segments
and embedded separators on the decoded value, then rebuild the upstream path
from re-encoded segments so the URL handed to fetch() is already normalized
and no traversal can survive parsing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The trionlabs8004 Supabase stack was migrated from the self-hosted
Dokploy docker-compose deployment to Supabase Cloud (2026-07-17) and the
self-hosted stack was decommissioned.

deploy-webapp-vps.yml waited for Dokploy to deploy the pushed commit to
the self-hosted compose stack, then health-checked containers
(trionlabs8004-migrate / -db / -edge-functions) that no longer exist. On
any push touching webapp/supabase/migrations|functions,
packages/indexer, config.ts or docker-compose.supabase.yml it would hang
15 min waiting for a deploy that never happens, then fail.

Cloud migrations/functions now ship via `supabase db push` /
`supabase functions deploy` against the migration-managed repo. A
cloud-side health-gate can be added later as a separate workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • .github/workflows/deploy-webapp-vps.yml
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f9b4bd4d-a64e-47f8-9f6a-8fb050372854

📥 Commits

Reviewing files that changed from the base of the PR and between 5ecfc5b and 513fae3.

📒 Files selected for processing (1)
  • .github/workflows/deploy-webapp-vps.yml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/remove-selfhosted-healthgate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yamancan
yamancan marked this pull request as ready for review July 18, 2026 00:51
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.

5 participants