Skip to content

feat(compose): parameterize sandbox image via SANDBOX_IMAGE - #54

Merged
Wilbert957 merged 1 commit into
mainfrom
feat/parameterize-sandbox-image
Jul 7, 2026
Merged

feat(compose): parameterize sandbox image via SANDBOX_IMAGE#54
Wilbert957 merged 1 commit into
mainfrom
feat/parameterize-sandbox-image

Conversation

@Wilbert957

Copy link
Copy Markdown
Collaborator

Problem

The sandbox service image was hardcoded to the aliyun VPC registry:

image: eliza-registry-vpc.ap-southeast-1.cr.aliyuncs.com/eliza/0g-sandbox:sandbox

That endpoint only resolves/connects inside the aliyun VPC. On GCP CVMs (e.g. wei-tapp) it's unreachable — DNS no such host for the -vpc ref, and the public aliyun endpoint times out from GCP. The only way to deploy there was to docker build the image locally on each host and set pull_policy: never — not reproducible, and it breaks a fresh CVM (no image present).

Change

Make the image reference an env var with the aliyun ref as default:

image: ${SANDBOX_IMAGE:-eliza-registry-vpc.ap-southeast-1.cr.aliyuncs.com/eliza/0g-sandbox:sandbox}
pull_policy: always

Each deployment sets SANDBOX_IMAGE in .env to a registry its host can reach. .env.example documents it, including the GCP Artifact Registry copy for GCP CVMs.

  • Compose text is identical across hosts → stable composeHash for TappRegistry.
  • The image content digest is the same wherever it's mirrored → imageHashes unaffected.
  • Drops the per-host local-build + pull_policy: never workaround.

Verified end-to-end (GCP CVM, us-central1)

  • Pushed the image to a new AR repo us-central1-docker.pkg.dev/g-devops/zg-sandbox/0g-sandbox:sandbox.
  • Set SANDBOX_IMAGE to that ref, tapp-cli docker-login with a short-lived gcloud auth print-access-token, removed the local image, start-app.
  • Sandbox container came up pulled from AR (verified docker ps image ref), all 12 services healthy, billing proxy signer: aligned.

Notes / follow-ups

  • Pull auth: AR is private; the CVM instance service account lacks the AR scope, and creating a dedicated reader SA key is currently blocked by IAM. So pull auth is done out-of-band per deploy via tapp-cli docker-login with a ~1h token — fine for attended deploys. Unattended auto-pull would need an AR Reader SA key (admin action) or a public mirror.
  • Related host prerequisite (br_netfilter) documented in the PR feat(daytona): upgrade to v0.189 + Sysbox host-boundary isolation #51 comment.

🤖 Generated with Claude Code

The sandbox proxy image was hardcoded to the aliyun VPC registry
(eliza-registry-vpc.ap-southeast-1.cr.aliyuncs.com), which is only
reachable from inside that VPC — GCP CVMs cannot pull it, forcing a
local `docker build` + `pull_policy: never` workaround per host.

Make the image reference a `${SANDBOX_IMAGE:-<aliyun default>}` env var so
each deployment points at a registry its host can reach (e.g. a GCP
Artifact Registry copy for GCP CVMs) without editing the tracked compose.
Compose text stays identical across hosts (stable composeHash); only .env
differs, and the image content digest is the same wherever it's mirrored.

Pull auth for private registries is set up out-of-band via
`tapp-cli docker-login` (AR: oauth2accesstoken + `gcloud auth print-access-token`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Wilbert957
Wilbert957 merged commit 7843489 into main Jul 7, 2026
Wilbert957 added a commit that referenced this pull request Jul 9, 2026
* feat(proxy): publicPorts — per-port public preview for sandboxes

Create requests may now carry "publicPorts": [8080, ...]; only listed ports
are publicly reachable, the rest fall back to Daytona's private-sandbox auth.
Enforcement lives in the 0g-daytona fork (v0.189.0-0g.1 images); this side:

- validate at the boundary (range/reserved/cap; sealed must include 8080)
- round-trip check: if the Daytona backend drops the field (stock image),
  fail the create with 502 and stop the orphan instead of silently handing
  out an unrestricted sandbox
- enrich create responses with ready-to-use preview_urls
- cmd/user --ports flag; compose api/proxy images parameterized
  (DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE, official images as default)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(compose): parameterize broker image and runner runtime

- BROKER_IMAGE: registry-agnostic broker image ref (same pattern as
  SANDBOX_IMAGE from #54)
- RUNNER_RUNTIME: sysbox-runc needs host kernel >=5.12 (shiftfs/idmapped
  mounts; on 5.10 sysbox cannot write resolv.conf). Default flips to runc —
  hosts that can run sysbox must now opt in with RUNNER_RUNTIME=sysbox-runc
  to keep the host-boundary isolation from #51.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(compose): self-host all third-party images under REGISTRY_PREFIX

Upstream Daytona went closed source (github main emptied; last open tag
v0.190.0), so Docker Hub availability of daytonaio/* images is not
guaranteed — and everything else was one Hub outage away from blocking
deploys. All third-party images are now mirrored into our own registry
(crane copy, upstream digests preserved — @sha256 pins unchanged) and
referenced via a single REGISTRY_PREFIX:

- daytona api/proxy: 0g-daytona fork v0.189.0-0g.1 (publicPorts) — replaces
  the per-image DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE vars
- daytona runner/ssh-gateway: v0.189.0 mirrors
- redis/postgres/dex/minio/registry: digest-pinned mirrors; the previously
  floating postgres:16-alpine (grant-admin-perms) is now digest-pinned too
- daytonaio/sandbox:0.5.0-slim mirrored as insurance (DEFAULT_SNAPSHOT ref
  unchanged for now — renaming it would re-key the registered snapshot)

Default prefix: GCP Artifact Registry. Aliyun-VPC hosts set REGISTRY_PREFIX
to the eliza mirror (push pending a fresh cr_temp_user token).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs+web: surface publicPorts in user frontend, CLI and API references

- web/user.html: "Public ports" input in the create modal (auto-adds 8080
  for sealed-only providers), request pass-through, and a ports badge on
  sandbox rows
- docs/CLI.md: --ports flag + response example with preview_urls
- docs/API_REFERENCE(.zh).md: publicPorts field, semantics, 502 behavior

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Wilbert957 added a commit that referenced this pull request Jul 16, 2026
#58)

* feat(proxy): publicPorts — per-port public preview for sandboxes

Create requests may now carry "publicPorts": [8080, ...]; only listed ports
are publicly reachable, the rest fall back to Daytona's private-sandbox auth.
Enforcement lives in the 0g-daytona fork (v0.189.0-0g.1 images); this side:

- validate at the boundary (range/reserved/cap; sealed must include 8080)
- round-trip check: if the Daytona backend drops the field (stock image),
  fail the create with 502 and stop the orphan instead of silently handing
  out an unrestricted sandbox
- enrich create responses with ready-to-use preview_urls
- cmd/user --ports flag; compose api/proxy images parameterized
  (DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE, official images as default)



* feat(compose): parameterize broker image and runner runtime

- BROKER_IMAGE: registry-agnostic broker image ref (same pattern as
  SANDBOX_IMAGE from #54)
- RUNNER_RUNTIME: sysbox-runc needs host kernel >=5.12 (shiftfs/idmapped
  mounts; on 5.10 sysbox cannot write resolv.conf). Default flips to runc —
  hosts that can run sysbox must now opt in with RUNNER_RUNTIME=sysbox-runc
  to keep the host-boundary isolation from #51.



* feat(compose): self-host all third-party images under REGISTRY_PREFIX

Upstream Daytona went closed source (github main emptied; last open tag
v0.190.0), so Docker Hub availability of daytonaio/* images is not
guaranteed — and everything else was one Hub outage away from blocking
deploys. All third-party images are now mirrored into our own registry
(crane copy, upstream digests preserved — @sha256 pins unchanged) and
referenced via a single REGISTRY_PREFIX:

- daytona api/proxy: 0g-daytona fork v0.189.0-0g.1 (publicPorts) — replaces
  the per-image DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE vars
- daytona runner/ssh-gateway: v0.189.0 mirrors
- redis/postgres/dex/minio/registry: digest-pinned mirrors; the previously
  floating postgres:16-alpine (grant-admin-perms) is now digest-pinned too
- daytonaio/sandbox:0.5.0-slim mirrored as insurance (DEFAULT_SNAPSHOT ref
  unchanged for now — renaming it would re-key the registered snapshot)

Default prefix: GCP Artifact Registry. Aliyun-VPC hosts set REGISTRY_PREFIX
to the eliza mirror (push pending a fresh cr_temp_user token).



* docs+web: surface publicPorts in user frontend, CLI and API references

- web/user.html: "Public ports" input in the create modal (auto-adds 8080
  for sealed-only providers), request pass-through, and a ports badge on
  sandbox rows
- docs/CLI.md: --ports flag + response example with preview_urls
- docs/API_REFERENCE(.zh).md: publicPorts field, semantics, 502 behavior



---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Wilbert957 added a commit that referenced this pull request Jul 20, 2026
…63)

* feat(proxy): publicPorts — per-port public preview for sandboxes (#57)

* feat(proxy): publicPorts — per-port public preview for sandboxes

Create requests may now carry "publicPorts": [8080, ...]; only listed ports
are publicly reachable, the rest fall back to Daytona's private-sandbox auth.
Enforcement lives in the 0g-daytona fork (v0.189.0-0g.1 images); this side:

- validate at the boundary (range/reserved/cap; sealed must include 8080)
- round-trip check: if the Daytona backend drops the field (stock image),
  fail the create with 502 and stop the orphan instead of silently handing
  out an unrestricted sandbox
- enrich create responses with ready-to-use preview_urls
- cmd/user --ports flag; compose api/proxy images parameterized
  (DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE, official images as default)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(compose): parameterize broker image and runner runtime

- BROKER_IMAGE: registry-agnostic broker image ref (same pattern as
  SANDBOX_IMAGE from #54)
- RUNNER_RUNTIME: sysbox-runc needs host kernel >=5.12 (shiftfs/idmapped
  mounts; on 5.10 sysbox cannot write resolv.conf). Default flips to runc —
  hosts that can run sysbox must now opt in with RUNNER_RUNTIME=sysbox-runc
  to keep the host-boundary isolation from #51.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(compose): self-host all third-party images under REGISTRY_PREFIX

Upstream Daytona went closed source (github main emptied; last open tag
v0.190.0), so Docker Hub availability of daytonaio/* images is not
guaranteed — and everything else was one Hub outage away from blocking
deploys. All third-party images are now mirrored into our own registry
(crane copy, upstream digests preserved — @sha256 pins unchanged) and
referenced via a single REGISTRY_PREFIX:

- daytona api/proxy: 0g-daytona fork v0.189.0-0g.1 (publicPorts) — replaces
  the per-image DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE vars
- daytona runner/ssh-gateway: v0.189.0 mirrors
- redis/postgres/dex/minio/registry: digest-pinned mirrors; the previously
  floating postgres:16-alpine (grant-admin-perms) is now digest-pinned too
- daytonaio/sandbox:0.5.0-slim mirrored as insurance (DEFAULT_SNAPSHOT ref
  unchanged for now — renaming it would re-key the registered snapshot)

Default prefix: GCP Artifact Registry. Aliyun-VPC hosts set REGISTRY_PREFIX
to the eliza mirror (push pending a fresh cr_temp_user token).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs+web: surface publicPorts in user frontend, CLI and API references

- web/user.html: "Public ports" input in the create modal (auto-adds 8080
  for sealed-only providers), request pass-through, and a ports badge on
  sandbox rows
- docs/CLI.md: --ports flag + response example with preview_urls
- docs/API_REFERENCE(.zh).md: publicPorts field, semantics, 502 behavior

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(serving): let an appOwner delegate a service to another provider wallet (#56)

Adds authorizeProvider/revokeProvider to SandboxServing so one appId can be
commercially represented by a wallet other than its TappRegistry owner,
without touching balance/nonce/voucher storage — each delegate keeps its own
fully isolated services/Account entry, exactly like a self-registered
provider today. This avoids the shared-balance overcommit race a naive
provider-supports-N-services rekeying would introduce across independently
deployed billing-proxy instances.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat!: v2 — provider IS the TEE signer (owner-managed services) (#60)

* docs: provider-is-signer v2 design (Chinese, for review)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(serving)!: v2 — provider IS the TEE signer

Provider addresses in the ledger (services key, voucher payee, balance
buckets, earnings) are now the TEE signer addresses of TappRegistry
nodes. Management moves to the appId owner:

- addOrUpdateService(signer, ...) — owner registers a node's service;
  signer must be an active TappRegistry node of the appId
- removeService(signer) — owner-called (the signer key dies with the
  machine); sweeps pending earnings to the owner, keeps user balances
  refundable and nonce watermarks intact
- withdrawEarnings(signer) — owner-called, pays the owner
- settlement now requires recovered == v.provider: a node can never
  settle vouchers naming another node as payee (cross-signing closed)
- drop #56 authorizeProvider/revokeProvider — delegate wallets no
  longer exist in this model

BREAKING: new deployment required (ledger key semantics changed);
not an in-place beacon upgrade. 34/34 Foundry tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(billing)!: derive provider identity from the TEE key; OWNER_ADDRESS config

- chain.Client: providerAddr = the TEE key's own address (provider IS the
  TEE signer); new ProviderAddress() accessor
- cmd/billing: all provider-keyed wiring (voucher payee, settler queue,
  pricing lookup, alerts, /api/providers, /api/info) uses the derived
  address; /api/info adds owner_address
- settler: rotation gate — while the local signer is not an active
  TappRegistry node, hold the queue instead of submitting (a fresh
  machine's vouchers would all settle INVALID_SIGNATURE and dead-letter
  real revenue until add-node-onchain lands); fail-open on RPC errors
- config: OWNER_ADDRESS replaces PROVIDER_ADDRESS (admin + display only);
  owner is ALWAYS an admin, ADMIN_ADDRESSES is now additive instead of
  replacing the default
- cmd/provider: register gains --signer and is signed by the owner key
  (OWNER_KEY env, PROVIDER_KEY legacy alias); deregister → remove-service;
  withdraw takes --signer and pays the owner; new rotate subcommand
  (re-register new signer with old terms + remove old, with runbook);
  authorize-provider/revoke-provider removed with the contract feature
- cmd/user: acknowledge sanity check updated — provider must be an active
  node of the appId (it is no longer the app owner)
- e2e_test: drop removed ProviderAddress config field (note: the e2e
  harness has pre-existing compile rot vs the current proxy.NewHandler
  signature, predating this change)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(broker,docs)!: v2 companion — broker top-up guard, OWNER_ADDRESS config surface, docs

- broker monitor: skip top-ups for providers whose service was removed
  on-chain (rotated-out TEE signer can never settle; deposits there would
  strand user funds behind the refund flow) + test
- compose/.env.example: OWNER_ADDRESS replaces PROVIDER_ADDRESS
- CLAUDE.md: identity-model section, updated register flow and env docs
- docs/CLI.md: register --signer / remove-service / rotate / withdraw
  rewritten for owner-signed management; rotation runbook
- contracts README (en+zh): v2 interface tables + design notes
- /api/info documents owner_address

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(dev): point dev environment at the v2 SandboxServing deployment

New dev stack (bound to TappRegistry 0x2Ce8…65c9):
  Proxy  0x3D0F2D62A60c8e62095671FfB23D15Cc4C98ca7c
  Beacon 0xBF04734BC87E12aB81E21bb4018b9bFa4c118721
  Impl   0x47a8E809Cd81b94eD19874da73C0E3F82DD90E5C (verified on chainscan)

Replaces the retired dev proxy 0x2024eB0C…E9b3 in contracts READMEs
(with an upgrade-history entry) and in the checkbal/checkuserbal
hardcoded defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(config)!: resolve app owner on-chain — drop OWNER_ADDRESS entirely

The appId owner is derivable: getAppInfo(BACKEND_APP_NAME).owner. Configuring
it duplicated on-chain truth and would drift on owner transfer.

- Handler.AppOwner: TTL-cached (1 min) chain lookup wired from cmd/billing;
  isAdmin = static ADMIN_ADDRESSES ∪ live owner; serves the stale value on
  RPC failure so a flaky RPC can't lock the owner out
- config: OwnerAddress field/env/validation removed; AdminList() is the
  extra-admins list only
- startup: logs resolved owner; errors on appId drift
  (services[signer].appId != BACKEND_APP_NAME)
- /api/info: owner_address resolved live, app_id added
- compose/.env.example/docs updated; config surface is now just
  BACKEND_APP_NAME (+ optional ADMIN_ADDRESSES)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(indexer): liveness = node membership, not app ownership

isLiveProvider still compared the provider address against
getAppInfo(appId).owner — a v1 assumption. In v2 the provider IS the
node's TEE signer and never equals the owner, so every v2 provider was
dropped and the broker marketplace (GET /api/providers) stayed empty.

Check tapp.getNode(appId, provider).addedAt != 0 instead (same fix
cmd/user acknowledge already got). Fail-open on RPC errors preserved;
revalidate() flows through the same path.

Reported in live verification on PR #60.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(dashboard): provider context = this node's TEE signer, not the wallet

The dashboard used the connected wallet as the provider address
(serviceExists/services/providerEarnings) and called the v1 ABI
(5-arg addOrUpdateService, no-arg withdrawEarnings) — in v2 the owner
logs in (the TEE signer has no connectable key), so every provider
panel showed Not Registered and the register/withdraw buttons reverted.

- provider context now comes from /api/info provider_address
  (one dashboard = one node; nothing to switch)
- ABI updated to v2: addOrUpdateService(signer,…), withdrawEarnings(signer),
  removeService(signer); register/withdraw txs are owner-signed
- connect hint explains the owner-wallet login model

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(billing): admin routes in main.go missed the dynamic owner check

Seven operator endpoints (registry pull/gc, sessions, audit-log, queue
summary/dlq/aggregate) gated on cfg.Chain.IsAdmin — the static
ADMIN_ADDRESSES list — so the appId owner got 403 on them (e.g.
dashboard image import) while handler-registered admin routes worked.

Route everything through Handler.IsAdmin (static list ∪ live app owner)
and delete the static config IsAdmin so future routes can't regress to it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(provider-cli): snapshot/gc subcommands use the admin (owner) key

snapshot / snapshots / delete-snapshot / gc-images sign EIP-191 admin API
requests — the signing wallet must be an admin (the app owner or an
ADMIN_ADDRESSES wallet), not the retired provider-wallet identity. Route
them through resolveOwnerKey (OWNER_KEY, PROVIDER_KEY legacy alias) and
drop the now-dead resolveKey helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(dashboard): show the app owner; disambiguate from the contract admin

The dashboard displayed only contract.owner() labeled "Owner" — the
proxy's upgrade admin, which in v2 is NOT who operates this node. Add an
"App Owner (admin)" row (from /api/info owner_address, with a tooltip
explaining it's the wallet to log in with) and relabel the contract row
to "Contract Admin". Provider row labeled "Provider (TEE signer)".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): v2 testnet deployment supersedes the v1 proxies

New testnet stack bound to the canonical TappRegistry 0x95a0…511c:
  Proxy  0x3490B9053AC46F7Bf71A1ceBffcB2be2C1405b41
  Beacon 0x79D6D7B5468AA134360bf73cc667FC63f704B62d
  Impl   0x7a1A5FC5B1A6AC1127e2D8b63400615B2ea49C47 (verified on chainscan)

v1 proxies 0xA07b0033…FC12c and 0x3d4d8a05…cf6f retired (refund-only).
Stake note now points at the registry's minStakeAmount instead of a
hardcoded historical value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): testnet v2 proxy is bound to TappRegistry 0x2Ce8

Deployed pointing at 0x95a0 by mistake; repointed to 0x2Ce8 via
setTappRegistry (tx before any state existed — clean switch, matches
testnet-aliyun.env).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(skill): user skill — snapshot catalog + v2 drift fixes

- Step 5: standard snapshots with fixed specs (ubuntu 1C/1G, openclaw
  2C/4G) and the snapshot-vs-custom-resources rule (API rejects --cpu
  with --snapshot; verified live)
- providers example: provider address IS the TEE signer in v2, no
  separate signer field; prices are per-minute
- troubleshooting: signerVersion → TappRegistry ackVersion, re-ack also
  triggers on node changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(skill): snapshot names match the live testnet (0g-ubuntu22 / 0g-openclaw)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): restore 0x2Ce8 registry fix (accidentally reverted by f1abc9d)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): dev section — add TappRegistry (0x2Ce8) to table and env snippet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): zh dev section — add TappRegistry row and env line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(skill): regression fixes — sealed/ports coverage, upload API change, OpenClaw auth model

Live regression against the v2 testnet (v0.1.1 CLI) found:

- sealed: bare --sealed is rejected (needs --snapshot; image must be in the
  provider's internal registry) — example fixed, preconditions documented
- publicPorts: --ports was entirely undocumented; added usage, rules
  (max 16 / system ports / immutable / sealed⇒8080), preview_urls, and
  https proxy protocol on the hosted testnet
- toolbox files/upload: v0.189 switched to multipart — JSON base64 body is
  dead; Mode A fallback now uploads via exec + base64
- OpenClaw (three drifts): claude setup-token output is an OAuth token
  (sk-ant-oat…), NOT an ANTHROPIC_API_KEY — register via ; current openclaw refuses lan bind without a pre-set gateway
  auth token (no longer auto-generated); nohup inside exec dies with the
  session — use setsid (verified: gateway survives, port 200)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(api): /api/providers — provider is the derived TEE signer; deregisterService → removeService

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(skill): rewrite provider skill for the v2 identity model

The old skill predated even the TappRegistry migration (register
--tee-signer, signerVersion, provider-wallet stake in SandboxServing).
Full rewrite:

- identity model up front: provider = node TEE signer (ledger only),
  owner does all management (OWNER_KEY), nothing configured in .env
- first-time setup: tapp-cli register-onchain + authorizeInvalidator +
  register --signer; .env-change ⇒ volumesHash ⇒ ackVersion warning
- new sections: rotation runbook (settler queue-hold, add-node-first,
  rotate CLI, refund path) and resource quotas (org quota is the only
  total cap; DEFAULT_RUNNER_* useless; fresh-install env vs live-DB SQL)
- withdraw --signer pays the owner; snapshots via OWNER_KEY; standard
  base snapshots 0g-ubuntu22 / 0g-openclaw
- troubleshooting rebuilt from live incidents (402 after env change,
  held voucher queue, admin-only, imageHashes empty, SSH host display)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* docs(skill): new-domain checklist + domain drift from the testnet rename (#61)

- user skill: provider domain examples updated to provider-private-sandbox.0g.ai
  (broker URL unchanged)
- provider skill: new 5-item domain checklist (apex→8082, wildcard→4000,
  wildcard cert, tcp 2222, grey-cloud) with the acceptance probe, plus two
  troubleshooting rows (broker offline via orange-cloud WAF; wildcard hitting
  the nginx default page)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(plugin): bump plugin version to 2.0.0 (#62)

* chore(plugin): bump marketplace plugin version to 2.0.0 for the v2 release

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(plugin): bump plugin.json to 2.0.0 as well

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant