Skip to content

payments-stripe: retrofit into offchain pipeline conventions#36

Closed
tuky191 wants to merge 5 commits into
add-payments-stripe-toolfrom
chore/payments-stripe-offchain-conventions
Closed

payments-stripe: retrofit into offchain pipeline conventions#36
tuky191 wants to merge 5 commits into
add-payments-stripe-toolfrom
chore/payments-stripe-offchain-conventions

Conversation

@tuky191

@tuky191 tuky191 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Successor to #32 (Mike Hanono's original payments-stripe addition). #32 landed at the pre-#33 layout (tools/, per-tool deploy workflows, root Cargo.lock); this branch realigns the tool to the offchain pipeline conventions that landed in #33 so CI picks it up automatically.

Mike's tool code is unchanged in intent — this is purely a layout/convention conversion.

What changed

Area Before (from #32) After (this PR)
Tool path tools/payments-stripe/ offchain/tools/payments-stripe/
Tool discovery tools/payments-stripe/paths.json (hand-maintained) --meta output from bootstrap!() (auto)
Build manifest none tools.json + build.rs (byte-identical to math's)
Cargo.toml no [[bin]] / [build-dependencies] added per convention
fqn!() macros bare strings fqn!("...@1") threaded fqn!(concat!("...@", env!("TOOL_FQN_VERSION")))
Deploy workflows deploy-payments-stripe-{mainnet,testnet}.yml (per-tool) removed — offchain-tools.{discover,deploy,prepare,register}.yml matrix picks the tool up
Per-tool deploy artifacts tools/payments-stripe/deploy/{Dockerfile,cloud-run.*,register.sh} removed — shared offchain/Dockerfile is used by offchain-tools.deploy.yml

Verification

  • cargo check -p payments-stripe → passes
  • .pre-commit/14-check-tool-conventions → ✅ Tool conventions OK

Test plan

  • @mike-hanono sanity-checks the FQN strings (they're load-bearing for on-chain registration — xyz.taluslabs.payments.stripe.<endpoint>@<version> form preserved)
  • CI build on this PR completes (image push to GCR with the content-hashed tag)
  • When ready to deploy, push a testnet-* tag from main post-merge to fire the full chain pipeline against testnet

Original PR

Closes #32 once merged.

🤖 Generated with Claude Code

loverdos and others added 5 commits May 21, 2026 22:54
Adds the `memory-memwal` tool bundle: seven operations (`remember`,
`remember_bulk`, `recall`, `ask`, `analyze`, `forget`, `stats`) against a
MemWal relayer for SEAL-encrypted memory on Walrus over Sui.

Pinned to relayer tag `@mysten-incubation/memwal@0.0.4`; `health_check`
enforces the version against `GET /health` at runtime.

Wire protocol: Ed25519-signed requests with canonical message
`{ts}.{METHOD}.{path}.{sha256hex(body)}.{nonce}.{account_id}` and a fresh
UUID v4 nonce per request. Boot-time URL validation (`https://` only;
`MEMWAL_ALLOW_INSECURE=1` for dev). Delegate key parsed once into an
`Arc<SigningKey>`; hex intermediates zeroized; `KeyValidation` `Debug`
redacts the `Ok` variant. Upstream errors map to terse client reasons;
full body logged at `target = "memwal::upstream"`.

Process-wide `reqwest::Client` via `OnceLock` with `gzip`/`brotli`. `429`
surfaced as `RateLimited { retry_after_secs }`. Polling uses exponential
backoff (100 ms → 4 s within a 60 s budget) with 0–25% jitter.
`MAX_BULK_ITEMS = 20`; 1 MiB cap on text inputs.

A `just memwal …` module wraps server lifecycle, per-tool curl
invocations, and an end-to-end `demo` recipe.
* docs: offchain tools pipeline design

* docs: offchain tools pipeline implementation plan

Bite-sized task plan implementing the design in
docs/superpowers/specs/2026-05-19-offchain-tools-pipeline-design.md.
Eight phases: repo reorg, per-tool conventions (tools.json + build.rs
across 7 crates), shared Dockerfile, five composite GH actions, five
callable workflows (discover/deploy/prepare/register/readiness),
top-level ci.yml orchestrator, README + bootstrap notes, final
verification.

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

* offchain: move workspace, tools, and helpers into offchain/

Prepares the repo for a future onchain/ sibling. Files relocated via
git mv to preserve history. Workspace builds verified with cargo check.

* onchain: add placeholder dir for future Move tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* justfile: point modules into offchain/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* just: fix import + .nightly-version paths after offchain/ move

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: repoint coverage, sync-docs, pre-commit deps to offchain/ paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* pre-commit: cd into offchain/ before cargo invocations

Recipes run with PWD = .pre-commit/ after the offchain/ move, so cargo
could no longer find Cargo.toml. Each cargo-* recipe now cds into
../offchain first. Verified cargo-check and cargo-clippy succeed.

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

* tools/math: add tools.json, build.rs, [[bin]] declaration

Establishes the per-tool convention used by the offchain CI pipeline.
build.rs validates the bin/tool name match at compile time and injects
TOOL_FQN_VERSION from env (default "1" for local builds).

* tools/exchanges-coinbase: add tools.json, build.rs, [[bin]] declaration

* tools/http: add tools.json, build.rs, [[bin]] declaration

* tools/llm-openai-chat-completion: add tools.json, build.rs, [[bin]] declaration

* tools/social-twitter: add tools.json, build.rs, [[bin]] declaration

* tools/storage-walrus: add tools.json, build.rs, [[bin]] declaration

* tools/templating-jinja: add tools.json, build.rs, [[bin]] declaration

* offchain: update Cargo.lock for toml build-dep

build.rs in each tool crate pulls in toml = "0.8". The lockfile update
was missed across the seven per-tool commits, breaking
cargo check --workspace --locked on a fresh clone.

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

* offchain: add shared multi-stage Dockerfile

Build args PACKAGE, BINARY, TOOL_FQN_VERSION. Three stages: rust slim
builder with buildkit cache mounts, debian lib-collector via ldd, and a
distroless cc-debian12 final image. Smoke-tested locally with math.

* ci/actions: add install-sui composite (suiup + cache)

* ci/actions: add install-nexus-cli composite

* ci/actions: add gcp-auth-protocol composite

* ci/actions: add gcp-auth-infra composite

* ci/actions: add retrigger-pr composite (signed empty commit)

* ci: add offchain-tools.discover callable workflow

Globs offchain/tools/*/tools.json, computes per-tool content versions
(sha256(git rev-parse HEAD:<dir>/) first 8 hex chars as u32), and emits
two matrices: all tools and tools changed since base-ref. A change to a
shared file (Cargo.toml/Lock, Dockerfile, rust-toolchain) treats every
tool as changed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: add offchain-tools.deploy callable workflow

Per-tool matrix build of offchain/Dockerfile. Pushes to GHCR
(ghcr.io/<repo>/<tool>:sha-<7>) and GCR (gcr.io/<infra>/nexus-tools/<tool>:sha-<7>)
when dry-run=false; only builds (no push) when dry-run=true.

* ci: add offchain-tools.prepare callable workflow

Per-tool matrix: pulls the pushed image, runs <command> --meta, renders
the Cloud Run config, generates signed-HTTP keys (idempotent on FQN set),
uploads tool config to GCS. Aggregator job builds the manifest and the
offchain-tools-prepare GitHub artifact consumed by register.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: add offchain-tools.register callable workflow

Single-job (serialized Sui state) registration. Skips already-registered
FQNs; for new ones, runs the PTB, extracts OwnerCap<OverTool>, saves
registration JSON to GCS, registers signing key, reconciles the
toolkit-config secret with the on-chain tool_kid (sole-writer rule).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: add offchain-tools.readiness PR merge gate

Per-tool matrix that checks GCS for the Cloud Run config + at least one
FQN registration JSON. Fails red if either is missing, with a message
pointing the author at the publish workflow_dispatch.

* ci: add top-level orchestrator ci.yml

Wires discover/deploy/prepare/register/readiness per the trigger matrix:
- PR (any base): discover, dry-run deploy on changed matrix.
- PR with base testnet/mainnet: also readiness gate.
- Push to main: discover, full deploy push (no chain ops).
- Push to testnet/mainnet: discover, deploy, prepare, register on full matrix.
- workflow_dispatch with PR#: resolves PR head, runs full chain, then
  retrigger-pr flips readiness green.
- ci-gate aggregates job results for branch protection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: README — document offchain/ layout and deploy flow

* docs: add offchain bootstrap notes for first deploy

* ci/register: drop workflow-level env block

vars.* lookups in a workflow-level env block are resolved before the
job's environment: is bound, so NEXUS_TAG and SUI_CHANNEL would be
empty. Reference vars.* directly in the steps that need them.

* ci: fix PR target-ref env-resolution

On pull_request events, github.ref_name is the merge-ref (e.g.
"123/merge"), not the base branch. Without explicit handling, PRs to
mainnet/testnet would resolve to the wrong environment in callable
workflows. Prefer github.base_ref on pull_request.

* ci: align matrix selection with spec + tighten dispatch gating

Two changes:
- Push to main now uses matrix-changed (per the design.md trigger
  table). matrix-all on main was a deviation from spec.
- workflow_dispatch without a pr-number no longer fires prepare/register.
  Previously, a bare dispatch from any branch (including main) would
  try to register against the resolved environment.

* ci/discover: quote literal path in case pattern

case ${DIR}/*) was unquoted; quoting the literal prefix avoids issues
with shell metacharacters in DIR (low real risk; DIR comes from
dirname over a hardcoded glob, but the quoted form is cleaner).

* ci/deploy: consolidate image tags into a single output

Dry-run runs no longer emit an empty trailing tag line. The "Compute
image tags" step writes a heredoc-encoded multi-line list, and the
build-push step consumes a single output.

* pre-commit: use 'set working-directory' for cargo recipes

Cleaner than prefixing every cargo recipe with `cd ../offchain &&`.
Recipes that need repo root (md-lint, typos, shebang checks) already
cd themselves; check-container-runtime doesn't touch the filesystem.

Works on just >= 1.33 (the file-level setting predates the per-recipe
[working-directory] attribute from 1.38).

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

* ci/deploy: build via --manifest-path to fix http crate-name ambiguity

cargo build -p http was ambiguous because the workspace member `http`
shares its name with transitive crates.io deps (http@0.2.12, http@1.4.0).
Switch the Dockerfile to use --manifest-path scoped to the tool's
Cargo.toml, which uniquely identifies the workspace member. Pass TOOL_DIR
as a build-arg from deploy.yml so the matrix's `dir` field flows through.

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

* ci/coverage: run cargo llvm-cov from offchain/ workspace dir

Cargo.toml lives under offchain/ now; running cargo llvm-cov from repo
root failed to locate it. Set working-directory: offchain on the
Generate Coverage step; emit lcov.info one level up so codecov-action
still picks it up at ./lcov.info.

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

* helpers/npm-install-g: resolve txt path via script dir

The previous `done < helpers/npm-install-g.txt` was relative to PWD =
repo root, which broke after the helpers/ move under offchain/. Use
BASH_SOURCE-derived SCRIPT_DIR so the txt is found regardless of where
the script is invoked from.

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

* docs: gitignore docs/superpowers/, untrack planning artifacts

The spec/plan/notes under docs/superpowers/ are internal brainstorming
output and don't need to be in git. They follow brainstorming/writing-plans
conventions (mixed ordered-list styles, fence variations) that conflict
with the project's MD029/MD031/MD032 markdownlint rules, causing
spurious md-lint failures. Move them to local-only and drop the README
back-reference.

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

* ci: enable deploy-from-branch iteration paths

Adds two ways to fire the full chain pipeline (deploy + prepare +
register) against testnet/mainnet without merging to main:

1. Push trigger on iterate/testnet/** and iterate/mainnet/**
   git push origin HEAD:iterate/testnet/bootstrap-1 fires the chain
   against testnet using that branch's content. Branch prefix selects
   the target env.

2. workflow_dispatch input target-env (testnet|mainnet)
   gh workflow run ci.yml --ref <branch> -f target-env=testnet runs the
   chain against testnet using the dispatched ref. Requires ci.yml on
   the default branch for the dispatch button to be discoverable; once
   landed, this is the ergonomic iteration loop.

Existing paths unchanged: PR (dry-run), push to main (build only),
push to testnet/mainnet (full chain), dispatch with pr-number (full
chain against PR head + retrigger-pr).

The env-resolution chain in deploy/prepare/register's target-ref now
honors (in order): PR base from resolve-target, inputs.target-env,
iterate/<env>/ branch prefix, PR base on pull_request, github.ref_name.

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

* ci/register: rename SUI_DEPLOYER_MNEMONIC to SUI_DEPLOYER_PK

The secret holds a `suiprivkey...` bech32 private key (sui keytool
export format), not a mnemonic. Rename to match reality. Same secret
will drive future onchain Move deploys via `sui client publish`, so
the bech32 form is the right canonical store.

Workflow flow unchanged: sui keytool import converts the bech32 PK to
the base64 keystore entry, then we read it back and pass to nexus
conf set --sui.pk.

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

* ci/prepare: bake NEXUS_TAG into each tool's Cloud Run config blob

Each per-(tool, version) blob written to
gs://<bucket>/<network>/offchain/tools/<tool>-v<version>.json now
carries nexus_contracts_tag captured from vars.NEXUS_TAG at prepare
time. Terraform reads this per-entry so the allowed-leaders mount path
stays pinned to the nexus version the tool was deployed against.

Upgrades become content-addressed: bump NEXUS_TAG, push, and only new
deployments pick up the new tag. Existing services keep their original
mount until explicitly torn down (delete the GCS blob and apply).

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

* ci: trigger tf-nexus-tools terraform apply after successful register

After deploy + prepare + register complete, dispatch tf-nexus-tools'
terraform.yml workflow against the `production` ref so Cloud Run
services / ALB / DNS records pick up the new tool's GCS state blob.

Uses the org-level GH_ACCESS_TOKEN PAT (same pattern ava-game uses for
all cross-repo gh operations) because the default GITHUB_TOKEN is
single-repo-scoped and cannot dispatch workflows in other repos.

Fires on every chain-op path: iterate/* push, push to testnet/mainnet,
and workflow_dispatch with pr-number or target-env.

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

* ci/actions: drop token_format from gcp-auth-infra (use Direct WIF)

token_format: 'access_token' requires google-github-actions/auth to
impersonate a real service account, which we don't have configured for
this WIF setup. Removing token_format makes the action use Direct WIF —
the federated token is still exposed via the auth_token output, which
docker/login-action consumes for `oauth2accesstoken` GCR auth.

Mirrors ava-game's pattern in protocol.deploy.yml.

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

* ci/deploy: split build (no auth) from push (auth) to avoid token expiry

Previously a single build-push-action did both compile + push, which
holds the GCP access token across the slow Rust compile. With cold
buildkit caches, that compile can exceed the 1h token lifetime and the
push fails at the end.

Mirror ava-game's protocol.deploy.yml pattern:
  1. Build (push=false) — slow Rust compile, no auth needed.
  2. Login to GHCR + GCP auth — fresh tokens minted here.
  3. Push (push=true) — reuses cached layers via cache-from=gha,
     completes within the token's 1h window.

Same gha cache scope is used for cache-from/cache-to on both steps so
the push step pulls the just-built layers instead of recompiling.

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

* ci: rename WIF provider from nexus-tools-protocol to nexus-tools

ava-game uses two separate WIF providers per env (ava-game and
ava-game-protocol). For nexus-tools we only configured a single
nexus-tools provider in tf-talus-infra/params/production/github.json,
and granted GCS bucket / Secret Manager IAM on the same principal in
tf-nexus-tools (iam.tf + bucket.tf).

The -protocol suffix was inherited from blindly mirroring ava-game's
prepare/register/readiness scaffolds. Drop it; both gcp-auth-protocol
and gcp-auth-infra now hit the same nexus-tools provider.

Permissions verified:
  artifactregistry.writer/reader  (tf-talus-infra)
  storage.objectAdmin on bucket   (tf-nexus-tools/bucket.tf)
  secretmanager.admin prefix=nexus-tools-* (tf-nexus-tools/iam.tf)

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

* ci: add missing checkout to prepare aggregate + readiness jobs

Both jobs reference local composite actions (./.github/actions/
gcp-auth-protocol) but never check out the repo, so the action's
action.yml is not present on disk when GHA tries to resolve it.

Per-tool prepare matrix already has checkout. Only the aggregate fan-in
job and the readiness gate job were missing it.

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

* ci/register: add tracing + explicit meta-file check

Previous run exited 1 right after "Using coin: ..." with no visible
error. set -e killed the script silently somewhere in the first for-loop
iteration. Add:
  - set -x for full command tracing
  - ls of $(dirname MANIFEST) and META_DIR before the loop
  - cat of the manifest
  - explicit `[ -f $META ]` guard with directory listing on miss
  - per-tool echo at loop top + values dump

Strictly diagnostic; once we see the failing command we can revert or
keep the parts that pay for themselves.

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

* ci/register: tolerate empty string in to_bytes()

The exchanges-coinbase tool's `get-spot-price` FQN returns an empty
.description from --meta. The to_bytes() pipe `... | grep '0x' | ...`
finds no match on empty input → grep exits 1 → pipefail propagates →
the surrounding `DESC_VEC=$(to_bytes "$DESC")` assignment fires set -e
and kills the register script silently right after "Using coin: ...".

Short-circuit to_bytes() to return empty (and exit 0) when input is
empty. The resulting Move literal `vector[]` is a valid empty vector,
so the PTB registers the tool with an empty description string.

(Empty descriptions are still a quality issue worth fixing upstream in
the tool's --meta output, but that's separate.)

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

* tools: thread TOOL_FQN_VERSION into every fqn!() call site

Every FQN literal across the 7 tools was hardcoded as @1, ignoring the
TOOL_FQN_VERSION env that build.rs + Dockerfile + CI matrix plumb
through end-to-end. As a result, register-on-chain saw @1 forever and
no content-addressed versioning ever reached the chain.

Mirror ava-game's pattern: each `fqn!("ns.tool@1")` becomes
`fqn!(concat!("ns.tool@", env!("TOOL_FQN_VERSION")))`. The fqn! macro
from nexus-sdk accepts any &'static str expression, so the concat!+env!
expression works inline; no per-tool TOOL_FQN const wrapper required.

Verified locally:
  TOOL_FQN_VERSION=999 cargo build -p math --release
  ./target/release/math --meta | jq -r '.[].fqn'
  → xyz.taluslabs.math.i64.{add,mul,cmp,sum}@999

48 FQN sites rewritten across all 7 tools. No stray @1 left.

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

* ci/register: drive registration via `nexus tool register offchain --from-meta`

Replaces the hand-rolled PTB loop (which drained a single COIN_ID across
all FQNs and failed with InsufficientGas after ~33 calls) with one CLI
invocation per FQN. The CLI acquires/releases a gas coin per call, so
the loop is no longer gas-bound. Idempotent: already-registered FQNs
return `{"already_registered": true}` and the loop preserves the
existing GCS record.

GCS layout adds `owner_cap_over_gas` alongside `owner_cap_over_tool` per
FQN. Existing fields (`signing_key_hash`, `tool_kid`) survive via
read-modify-write merge.

Drops the PTB-only `Load Nexus objects` step and the `to_bytes`,
`COIN_ID`, and `set -x` plumbing that went with it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: treat MoveAbort in register_tool_@14 as already-registered

The v1.0.0 CLI flags already-registered by string-matching the outer
entry name `register_off_chain_tool_`, but EFqnAlreadyExists fires from
the inner helper `register_tool_` (tool_registry.move:594), so the
CLI surfaces it as a generic error. Detect that exact MoveAbort
signature (function_name=register_tool_, instruction=14) and treat it
as already-registered so re-runs over prior on-chain state succeed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: pin gas + collateral coins per call to avoid drain

The v1.0.0 CLI builds its gas pool with a single coin
(cli/src/sui.rs:294 `with_gas(vec![one_coin])`) picked by-order=0 at
startup; collateral picks by-order=1 from a fresh fetch
(tool_register_offchain.rs:88). Repeated CLI invocations kept hitting
the same coin and draining it below the 100M budget, yielding
InsufficientGas after ~11 successful PTBs.

Re-query the wallet per FQN, pin the largest coin as --sui-gas-coin and
the smallest distinct coin as --collateral-coin. Fresh object refs each
call, no implicit collision between the gas and collateral picks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: instrument loop with wallet/CLI debug dumps

Add per-FQN diagnostic groups so we can see what coins were picked,
what the CLI saw on stdin/stderr, and what stdout came back when
registration fails. Splits CLI stderr/stdout into separate buffers so
the JSON output stays parseable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: bump --sui-gas-budget to 500M to cover tool storage cost

The default 100M budget covers computation + ~67M storage for a small
schema (verified against the v0.8.2 spot-price register tx), but
larger schemas (twitter response shapes, llm chat-completion, etc.)
hit InsufficientGas at execution time with `command: None`. Wallet's
largest coin is 569M MIST, leaving 500M as the safe budget ceiling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: consolidate to 4 coins + 1 SUI budget + pre-flight balance check

Heavy schemas (twitter get-tweet, llm chat-completion) need ~1 SUI of
gas for storage cost — confirmed from the prior static deploy
(tf-talus-nexus-v2/.../social-twitter.json:gasBudgetAmount=1e9). The
deployer wallet's 22 small coins (largest 0.6 SUI) can't individually
cover that budget.

Three changes:
- Add a Consolidate Wallet step that merges the two smallest coins
  iteratively until 4 remain, levelling balances to ~2.7 SUI each.
- Bump --sui-gas-budget on each register call to 1 SUI.
- Pre-flight balance check: if the largest available coin is below
  the budget, fail loudly with "wallet drained" instead of
  surfacing an opaque RPC error from Sui validation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: pick any coin ≥ budget, rotate when prior drained

Previously: pick the largest coin, fail with "wallet drained" if it's
below budget. But the prior-largest can drop below budget after a
call while other coins still have plenty — failing on it instead of
rotating wastes the wallet headroom.

Now: filter to coins with balance ≥ budget, take the largest of
those. Only fail if no coin in the wallet meets budget.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: trigger-tf-apply waits for dispatched run; drop retrigger-pr

Two cleanups now that register is green and trigger-tf-apply
successfully reaches tf-nexus-tools:

- trigger-tf-apply now polls for the run it just dispatched (since
  `gh workflow run` returns no ID), then `gh run watch --exit-status`
  to inherit the conclusion. Register+apply become one atomic gate;
  ci-gate fails if terraform fails.
- Drop retrigger-pr (job + composite action). It was a hack for the
  workflow_dispatch+PR case to nudge readiness back to green; no
  longer needed once the apply is awaited inline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: preserve per-FQN URL path + force trailing slash

Two related fixes to make registered URLs leader-compatible:

1. Each FQN has its own URL path (e.g. /i64/add, /get-tweet) baked
   into the meta JSON by the toolkit binary. Previously we overrode
   --url with a base hostname for the entire tool, losing the path
   and causing the leader to call the wrong endpoint. Now we extract
   the path from the meta URL and combine with the per-network
   internal hostname.

2. The leader appends `invoke` via RFC 3986 relative-URL join:
     /i64/add/  + invoke = /i64/add/invoke ✓
     /i64/add   + invoke = /invoke         ✗ (basename replace)
   Force a trailing slash so the join produces the right endpoint.
   This matches the legacy v0.8 static-deploy URL convention.

Smoke-tested with the math DAG end-to-end on testnet — add/cmp/mul
all returned 200 from /i64/<op>/invoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: replace gh run watch with the-actions-org/workflow-dispatch

`gh run watch` fetches check annotations, which requires the
`checks:read` permission. Fine-grained PATs cannot carry that scope
(GitHub limitation), so our trigger-tf-apply job was failing with 403
even though the dispatched terraform run itself succeeded.

Switch to `the-actions-org/workflow-dispatch@v4.0.0` — the same action
ava-game uses for the same dispatch+wait pattern. It polls
`actions:read` endpoints which fine-grained PATs CAN carry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/register: skip already-registered FQNs via on-chain snapshot

Take a single `nexus tool list --json` snapshot at the top of the
register step and use it to skip the CLI call entirely for FQNs
already on chain. Each register call costs ~1-2s even when it
MoveAborts on EFqnAlreadyExists, so this turns a re-run from minutes
into seconds.

The existing post-call MoveAbort handler stays as a defensive net
against snapshot races (e.g. a parallel run registers the same FQN
between the snapshot and our call).

We still verify the GCS reg file exists for skipped FQNs so the
signing-key + toolkit-config steps don't run blind.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* tools: cargo +nightly fmt across all 48 fqn!(concat!()) sites

The earlier perl rewrite of fqn!("...@1") -> fqn!(concat!("...@", env!("TOOL_FQN_VERSION"))) left long single-line invocations.
Nightly rustfmt wraps them across three lines, which the pre-commit
03-cargo-nightly-fmt hook enforces.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: README — current state of the pipeline + tool-author guide

Replaces the original PR-opening README with one that reflects how the
pipeline actually works after the v1.0.0 cutover:

- Tool-author conventions (tools.json, build.rs, [[bin]], fqn!()
  threading, per-FQN URL paths).
- Branch model: main (no chain ops), iterate/testnet/**,
  iterate/mainnet/**, workflow_dispatch with target-env / pr-number.
- What "full pipeline" does end-to-end (discover → deploy → prepare →
  register → trigger-tf-apply).
- Operational notes on deployer wallet, NEXUS_TAG pinning, stale
  config cleanup, and the parallel-ALB cost tradeoff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: README — explicit lifecycle (PR → main → promote to env)

Add a Lifecycle section walking from "open a PR" through "merge to
main" to "promote to a deploy env" (long-lived branch merge, iterate
branch, or manual workflow_dispatch). Expanded the trigger table to
also list the long-lived testnet/mainnet branches that are wired into
ci.yml.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/codecov: make patch coverage informational

Patch check was blocking PRs whose changes are mechanical and don't
warrant new tests — e.g. the 48-site fqn!(concat!()) rewrite where
every tool's `fn fqn()` body changed but is still a one-line const
return. Project-level check stays strict and still gates real
coverage regressions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* pre-commit: check-tool-conventions enforces fqn!() threading

Catches the one tool-author contract rule the build system can't:
fqn!("xyz...@<literal-version>") compiles fine but ships the wrong
content-version on chain (or collides with legacy @1 registrations).

The hook scans offchain/tools/*/src/ for any fqn!("...@<digits>")
literal and fails the commit with a pointer to the correct pattern.
Also asserts each tool dir has tools.json + build.rs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci/discover: source BLOCKED_TOOLS from repo variable

Skip-list now read from the repo-level `BLOCKED_TOOLS` variable
(space-separated tool_name values). Security blocks can be flipped
via repo settings → Variables without a code change. Empty/unset
variable means no tools blocked.

Initial value (set via gh variable set):
  BLOCKED_TOOLS=http

Blocks the http tool pending its SSRF guard. Both the on-chain
unregister and this CI block keep it from re-shipping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: tag-driven deploy (testnet-*/mainnet-*); drop long-lived deploy branches

Switch the canonical promotion path from "merge to long-lived
testnet/mainnet branch" to "push a testnet-*/mainnet-* tag". Tags are
immutable, so each deployment is attributable to an exact ref — no
merge-strategy gotchas (squash collapsing shared ancestry and breaking
future promotes), no long-lived branches to keep clean, no
ephemeral promote/* branches needed.

ci.yml changes:
- `on.push.tags: [testnet-*, mainnet-*]` triggers the full chain.
- Target-env resolution adds tag-prefix matching above the existing
  iterate/<env>/** rules.
- Prepare gate accepts tag pushes; drops the now-unused testnet /
  mainnet long-lived branch matches.
- ref_type guards distinguish tag pushes from same-named branches.

README: branch & tag model section + lifecycle promotion step
rewritten around the new flow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* memory-memwal: cargo +nightly fmt across 7 fqn!(concat!()) sites

Same wrap-across-three-lines pattern nightly fmt applied to the other
48 sites after the original perl rewrite.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* add concurrency group

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mike's PR #32 added payments-stripe at the legacy `tools/` path with
per-tool deploy workflows. Main has since restructured (PR #33) so all
tools live under `offchain/tools/` and share a single `offchain-tools.*`
CI pipeline matrix. This branch realigns payments-stripe to the new
layout so the CI picks it up automatically.

Layout move:
  tools/payments-stripe/         → offchain/tools/payments-stripe/

Added (per the new conventions enforced by
.pre-commit/14-check-tool-conventions):
  - tools.json         (tool_name, command, environment)
  - build.rs           (byte-identical to other tools; threads
                        TOOL_FQN_VERSION env var via cargo:rustc-env)
  - Cargo.toml         [[bin]] + [build-dependencies] sections

Removed (legacy per-tool deploy artifacts replaced by the shared
offchain-tools.{discover,deploy,prepare,register}.yml matrix):
  - tools/payments-stripe/deploy/{Dockerfile,cloud-run.*,register.sh}
  - tools/payments-stripe/paths.json (paths now come from each tool's
                                      --meta output)
  - .github/workflows/deploy-payments-stripe-{mainnet,testnet}.yml

Source modifications:
  - 6 fqn!() macros across src/tools/*.rs threaded to the
    fqn!(concat!("...@", env!("TOOL_FQN_VERSION"))) form so the
    on-chain FQN version stays in sync with the content-hashed image
    tag CI produces.

Verification:
  - cargo check -p payments-stripe  → passes
  - .pre-commit/14-check-tool-conventions  → ✅ Tool conventions OK
@tuky191
tuky191 changed the base branch from main to add-nexus-tool-builder-skill May 25, 2026 15:59
@tuky191
tuky191 changed the base branch from add-nexus-tool-builder-skill to add-payments-stripe-tool May 25, 2026 16:00
@tuky191
tuky191 requested review from a team, loverdos and mike-hanono May 25, 2026 16:03
@tuky191 tuky191 closed this May 25, 2026
@tuky191
tuky191 deleted the chore/payments-stripe-offchain-conventions branch May 25, 2026 16:06
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.

2 participants