Skip to content

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

Merged
Wilbert957 merged 4 commits into
devfrom
feat/public-ports
Jul 9, 2026
Merged

feat(proxy): publicPorts — per-port public preview for sandboxes#57
Wilbert957 merged 4 commits into
devfrom
feat/public-ports

Conversation

@Wilbert957

Copy link
Copy Markdown
Collaborator

Summary

Sandboxes today are forced public: true (0G doesn't use Daytona OIDC), so every user port is world-reachable. This adds an optional "publicPorts": [8080, 3000] field on create: only listed ports are publicly reachable; all other ports fall back to Daytona's private-sandbox auth path (owner preview tokens still work — the restriction targets the public, not the owner). Omitting the field keeps today's all-ports-public behavior; existing sandboxes are untouched.

Enforcement lives in the 0g-daytona fork (0gfoundation/0g-daytona@0g/main, baseline v0.189.0, images daytona-{api,proxy}:v0.189.0-0g.1 on GCP AR):

  • api: sandbox.publicPorts jsonb column + migration, DTO validation, normalize (dedupe/sort/reject reserved 22222/2280/33333/reject empty/cap 16), proxy-only GET /preview/:id/public-ports
  • proxy: the public-skip gate in GetProxyTarget now consults the allowlist (hand-written API call, no api-client-go regen; 4xx degrades to unrestricted so the patched proxy works against a stock API; cached 1h — the field is immutable after create)

This PR (billing-proxy side):

  • boundary validation with clear 400s, incl. the sealed rule Daytona doesn't know: sealed + publicPorts must include 8080 (agent-fronting port)
  • round-trip check: a stock Daytona API silently strips unknown DTO fields — the user would get an unrestricted sandbox while believing ports are locked. If the create response carries no publicPorts, the billing proxy fails the create with 502 and stops the orphan
  • create responses gain preview_urls: {"8080": "http://8080-<id>.<PROXY_DOMAIN>"}
  • cmd/user create --ports 8080,3000
  • compose: DAYTONA_API_IMAGE/DAYTONA_PROXY_IMAGE params (official images default); also folds in BROKER_IMAGE + RUNNER_RUNTIME parameterization (note: runner runtime default flips sysbox-runc → runc; kernel ≥5.12 hosts must set RUNNER_RUNTIME=sysbox-runc to keep feat(daytona): upgrade to v0.189 + Sysbox host-boundary isolation #51's host-boundary isolation)

Test plan

  • fork proxy unit tests: allowlist gate (in-list pass / out-of-list auth / 404-degrade / cache single-fetch) — 4/4
  • internal/proxy unit tests: 15 validation cases + decorate supported/unsupported/no-domain — all pass
  • go build ./... && go test ./... green in both repos
  • E2E on local stack with fork images: publicPorts:[8080] sandbox with dual listeners — 8080 serves, 9090 blocked; unrestricted + sealed sandboxes unchanged (pending stack restart approval)

🤖 Generated with Claude Code

Wilbert957 and others added 3 commits July 8, 2026 21:15
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>
- 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>
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>
@Wilbert957

Copy link
Copy Markdown
Collaborator Author

E2E verified on wei-tapp (136.113.168.149, fork images v0.189.0-0g.1):

  • create with --ports 8080 → response echoes publicPorts: [8080] (fork API persists the field)
  • sandbox running listeners on 8080 + 9090:
    • http://8080-<id>.…nip.io:4000/200, content served
    • http://9090-<id>.…nip.io:4000/307 → dex auth (private-port path; public callers blocked)
  • regression: sandbox created without publicPorts → 9090 → 200 (all-ports-public behavior unchanged)
  • test sandboxes deleted

Note: the wei-tapp billing container is an older build, so preview_urls enrichment and the stock-backend 502 guard weren't exercised there (unit-tested in this PR); core enforcement is entirely Daytona-side and confirmed live. GCP firewall needed tcp:4000 opened (was 8080-8082 only) — done manually.

- 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
Wilbert957 changed the base branch from main to dev July 9, 2026 07:50
@Wilbert957
Wilbert957 merged commit b530704 into dev Jul 9, 2026
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