draft(ci): automate og-tdx CVM build → publish → attest (version×env×owner)#32
Merged
Conversation
DRAFT for review — not wired to a runner yet.
- .github/workflows/build-cvm.yml: dispatch(version,env,owner,publish) on a self-hosted
al8 runner → build (HARDEN per env, OWNER baked, KBS fixed) → smoke gate → publish
versioned image+family → compute+register reference values → PR them into dev.
- verifier/register-shared-as.sh: add the owner dimension (path <version>/<env>/<owner>.json,
policy id 0g-tapp-<version>-<env>-<owner>) — owner is measured (folded into initrd).
- gcp-cvm/ci/{gen-reference-values,fetch-inputs,open-refvalues-pr}.sh helpers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e; real ref-value generator - gcp-cvm/ci/build-cryptpilot-fde.sh: compile cryptpilot-fde-host from the 0gfoundation cryptpilot fork at a pinned commit (transparent/reproducible vs a prebuilt blob). The stock 0.7.0 errors "saved_entry not found" on a never-booted image; the fork falls back to the default grub.cfg menuentry. - gcp-cvm/ci/gen-reference-values.sh: rewritten to use the fixed binary's real JSON output (filter to the five measurement.<component>.SHA-384 keys via python; fail if any is empty). Validated end-to-end against og-tdx-dev.qcow2. - build-cvm.yml: build the fixed cryptpilot-fde as a step, pass it to the ref-value step. - reference-values/README.md: owner dimension (<version>/<env>/<owner>.json) + fork-build flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ucket/AS/fde-ref) All tunables now come from repo/org Variables (vars.*) with in-file defaults, so KBS URLs, GCP project/bucket, AS endpoint and the cryptpilot-fde fork commit can be changed in GitHub settings without editing the workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t dispatched ref - fetch-inputs.sh: support BASE_IMAGE_URL as file:// or absolute local path (COPY, never symlink, since Stage A mutates the input) — use the persistent runner cache /root/base-noble.qcow2. - workflow: BASE_IMAGE_URL from vars; smoke test continue-on-error (advisory until validated on a KVM runner); checkout the dispatched ref (not hardcoded dev) so the draft can be dry-run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…allback), not the ESP-convert branch Dry-run caught it: CRYPTPILOT_REF=52a77a2 (fix/gcp-convert-esp-sync) has no show-reference-value fallback, so gen-reference-values errored 'saved_entry not found' on a never-booted image. The fallback lives on fix/srv-default-entry @ 64fe4e744574c8e0273dd1e5d54ceae9714748a0 (falls back to the default grub.cfg menuentry; also has --hash-algo). Pin to the full SHA. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d (drop fix-B & keep-generic workarounds) Realizes what #130 was for: let cryptpilot-convert do the ESP-grub sync itself instead of our scripts. Validated end-to-end on GCP TDX hardware (boot → /data auto-provision → docker/sysbox → tapp-server). Toolchain (ci/setup-toolchain.sh, reproducible): install released cryptpilot 0.8.0 (crypt/verity/ fde-host + dracut) and overlay convert (#130) + fde-host (#128) built from the pinned fork commit 0gfoundation/cryptpilot@57d7512 (feat/0g-gcp-cvm = 0.8.0 + #128 + #130). In-image runtime = released 0.8.0 cryptpilot-fde-guest deb (fetch-inputs). Script simplification: - prepare-gcp-tapp.sh: DROP fix-B (convert #130 syncs the ESP grub.cfg + modules itself); add CRYPTPILOT_CONVERT override. - build-gcp-tapp.sh: FDE_PACKAGE -> 0.8.0 guest deb; config.toml gains max_log_files=7 (bounds /var/log/tapp on the RAM rootfs — issue #23). - ci/gen-reference-values.sh: use /usr/bin/cryptpilot-fde-host (from setup-toolchain). - ci/build-cryptpilot-fde.sh: removed (subsumed by setup-toolchain). - workflow: add "Set up 0.8.0 toolchain" step; ref-value step uses the installed fde-host. - add publish-gcp-image.sh (Stage C) to this branch. TODO (deferred): drop the leftover -generic kernel for a gcp-only image; wire tapp-server from dev in the real pipeline; re-verify prod (HARDEN=1) variant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…for custom owner - on: release (published) -> build the DEFAULT deployment (vars.DEFAULT_OWNER_ADDRESS), both env, publish + register. workflow_dispatch -> custom per-owner (owner input; blank = default). - resolve version/owner/publish for both triggers; matrix env defaults to both on release. - header comment updated to the 0.8.0 toolchain (ci/setup-toolchain.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consistent naming with the other vars.* config (no DEFAULT_ prefix); it still serves as the default/fallback owner for release builds and blank dispatch input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wilbert957
force-pushed
the
draft/cvm-ci-automation
branch
from
July 13, 2026 07:41
b90b4b2 to
849c2e8
Compare
Point gcloud/gsutil at a job-scoped CLOUDSDK_CONFIG under RUNNER_TEMP, write the key there only long enough to activate, delete it immediately, and revoke + wipe the config dir in cleanup. Nothing lands in the runner's ~/.config/gcloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
runner context isn't available in jobs.<id>.env — GitHub rejected the workflow file. Set CLOUDSDK_CONFIG in the auth step via $RUNNER_TEMP and export it into $GITHUB_ENV for later steps; guard cleanup when it's unset (publish=false). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wilbert957
marked this pull request as ready for review
July 13, 2026 08:21
Wilbert957
added a commit
that referenced
this pull request
Jul 16, 2026
… RPC (#58) * feat: runtime CLI↔server/contract version-compatibility check (#26) * feat: runtime CLI↔server/contract version-compatibility check Implements the compatibility mechanism from docs/VERSIONING.md. Rust (gRPC edge): - tapp-common::compat::interface_warning compares MAJOR.MINOR (PATCH ignored), returning a warning when the peer's interface looks incompatible. - tapp-cli build.rs stamps the expected server interface version (the workspace-root tapp-server MAJOR.MINOR) at build time — no number to maintain. - create_client does a best-effort GetTappInfo on connect and warns to stderr on mismatch; it never hard-blocks (a genuinely missing RPC fails at the call). - Bump tapp-cli 0.1.0 -> 0.1.1 (adds the on-connect warning behavior). Contract (on-chain edge): - Add a version() view to TappRegistry returning "0.1.0" so callers can read the live implementation version behind the stable beacon proxy address. - Rename local `version` vars to `ackVersion` to avoid shadowing the new view. Server needs no change: GetTappInfo already reports the full tapp-server version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(contract): record TappRegistry v0.1.0 upgrade (adds version()) Deployed new implementation 0x9Ea52Ef383e8eA3fe7F0890309D3C62b2FC1Ac2B behind the stable beacon proxy 0x2Ce8...165c9 on 0G Galileo testnet; beacon.upgradeTo tx 0x8a003a1a. version() now returns "0.1.0" on-chain, storage preserved, source-verified on chainscan-galileo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync Cargo.lock (tempfile dev-dependency) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gcp-cvm): add Stage C (publish to GCP) + document the 3-stage pipeline (#29) The build produced a qcow2 but stopped short of a GCP image. Add the "last mile": - publish-gcp-image.sh (Stage C): qemu-img convert qcow2 -> raw -> sparse oldgnu tar.gz -> gsutil cp -> gcloud compute images create with UEFI_COMPATIBLE,GVNIC,SEV_CAPABLE,TDX_CAPABLE. Parameterized (GCS_BUCKET/GCP_PROJECT/GUEST_OS_FEATURES, defaults gs://tapp-image + g-devops); refuses to clobber an existing image name. Kept separate from the build because it needs gcloud/gsutil auth + network upload. - build-gcp-tapp.sh: opt-in Stage C via PUBLISH_AS=<image-name> (runs publish after Stage B); also fix the now-stale tapp-data-provision.service Description (format-or-adopt, not "carve"). - Docs: README pipeline overview (Stage 0 base prep / A / B / optional smoke test / C), publish-gcp-image.sh in the file table, PUBLISH_AS env var, a "Publish to GCP (Stage C)" section; main doc §14 (Stage C steps + stages-at-a-glance). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: start-app --register-onchain — idempotent on-chain registration before the app starts (#28) * feat: start-app --register-onchain registers the app before it starts New optional flow for 'start-app': with --register-onchain (+ --rpc-url, --contract, --stake-wei), the CLI idempotently ensures the app is registered on-chain BEFORE its containers start: - app not registered -> registerApp (this node = first node) - registered, signer not a node -> addNode (per-node override iff differs) - signer already in node list -> skip, just start To get real hashes before anything runs, StartAppRequest gains a measure_only flag: the server stages files, 'docker compose pull's the images and computes compose/volumes/image hashes without starting containers, returning them via new TaskResult fields. The follow-up normal StartApp starts from the cached images. GetAppKey now creates the app key if missing (same as GetEvidence), so the signer address is available before the app starts; the key lives in server memory so the app gets the same key when it runs. e2e-tested against 0G Galileo testnet (registerApp + idempotent skip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document start-app --register-onchain in README, runbook, and skill Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix: make start-service idempotent, dropping the already-running pre-check (#30) start-service failed forever with 'Service <svc> is already running' after stop-service, whenever a running sibling service's name contained the stopped one (e.g. sandbox vs sandbox-redis): the pre-check did a substring match on docker compose ps output. (#27) Rather than fixing the match, drop the pre-check: docker compose up -d is already idempotent (running + unchanged config = no-op), and removing it also unblocks 'start-service --pull' on a running service, whose re-measure path existed downstream but was unreachable. Typo'd service names still fail the task with docker's own 'no such service' error. e2e-verified: restart-after-stop with prefix sibling running, no-op on running service, --pull on running service, and bad-name failure. Fixes #27 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * chore: bump tapp-server and tapp-cli to 0.2.0 Per docs/VERSIONING.md, for the changes merged to dev: - tapp-server 0.1.1 -> 0.2.0: gRPC interface changed (#28 adds StartAppRequest.measure_only + TaskResult measurement fields; #30 changes StartService semantics for already-running services) - tapp-cli 0.1.1 -> 0.2.0: new user-facing flags (#28: --register-onchain / --rpc-url / --contract / --stake-wei on start-app) - contract unchanged (0.1.0); tapp-common internal, additive only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(logging): bound file-log growth + move CVM logs off the RAM rootfs (#34) * fix(logging): bound file-log growth + move CVM logs off the RAM rootfs (#23) - RollingFileAppender now keeps at most logging.max_log_files daily files (new config field, default 7) — daily rotation alone never deletes old files, so total size was unbounded - gcp-cvm build: tapp-server file logs go to /data/log/tapp/ (persistent disk) instead of /var/log/tapp/ on the RAM-backed rootfs overlay, and tapp-server.service gains RequiresMountsFor=/data (same fail-loud policy as docker/containerd) Closes #23 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(logging): prune stale daily logs at startup, not only at in-process rotation tracing-appender's max_log_files only prunes inside refresh_writer (when the date rolls over while the process runs) — verified empirically: files left by previous runs survive until the first in-process midnight, and a process that never lives past midnight never prunes. Add a best-effort startup prune of {prefix}.* files before building the appender, with a unit test. Also fix the stale KmsClient::new arity in kms_client tests (predates the retry-config parameter; lib tests did not compile). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(server): pass derivation material through GetSecretResource to KMS /app-key (#35) - proto: GetSecretResourceRequest gains optional 'string material = 2' — hex-encoded derivation material, opaque to tapp, forwarded verbatim - kms_client: KmsRequest carries material; omitted from the JSON body when empty so the request stays byte-identical to the pre-material format (KMS derives purely from app_id, existing callers untouched) - tests: new wiremock test asserting material is forwarded verbatim and omitted when empty; also fix stale KmsClient::new arity in existing tests Closes #33 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): document max_log_files + GetSecretResource material; fix stale config sample - [logging] sample: path -> file_path (actual config key), add format and the new max_log_files retention cap (#34) - config sample and KMS section: [kms] cluster_urls -> [kbs] node_urls (the section/key tapp-server actually reads), port 8080 -> 9091 - KMS Integration: document the optional derivation material passthrough (#35) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): automate og-tdx CVM build → publish → attest (version×env×owner) (#32) * draft(ci): automate og-tdx CVM build/publish/attest (version×env×owner) DRAFT for review — not wired to a runner yet. - .github/workflows/build-cvm.yml: dispatch(version,env,owner,publish) on a self-hosted al8 runner → build (HARDEN per env, OWNER baked, KBS fixed) → smoke gate → publish versioned image+family → compute+register reference values → PR them into dev. - verifier/register-shared-as.sh: add the owner dimension (path <version>/<env>/<owner>.json, policy id 0g-tapp-<version>-<env>-<owner>) — owner is measured (folded into initrd). - gcp-cvm/ci/{gen-reference-values,fetch-inputs,open-refvalues-pr}.sh helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): build the #128-fixed cryptpilot-fde from pinned fork source; real ref-value generator - gcp-cvm/ci/build-cryptpilot-fde.sh: compile cryptpilot-fde-host from the 0gfoundation cryptpilot fork at a pinned commit (transparent/reproducible vs a prebuilt blob). The stock 0.7.0 errors "saved_entry not found" on a never-booted image; the fork falls back to the default grub.cfg menuentry. - gcp-cvm/ci/gen-reference-values.sh: rewritten to use the fixed binary's real JSON output (filter to the five measurement.<component>.SHA-384 keys via python; fail if any is empty). Validated end-to-end against og-tdx-dev.qcow2. - build-cvm.yml: build the fixed cryptpilot-fde as a step, pass it to the ref-value step. - reference-values/README.md: owner dimension (<version>/<env>/<owner>.json) + fork-build flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): move fixed config to repo Actions Variables (KBS/project/bucket/AS/fde-ref) All tunables now come from repo/org Variables (vars.*) with in-file defaults, so KBS URLs, GCP project/bucket, AS endpoint and the cryptpilot-fde fork commit can be changed in GitHub settings without editing the workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): local base-image cache + non-blocking smoke test + checkout dispatched ref - fetch-inputs.sh: support BASE_IMAGE_URL as file:// or absolute local path (COPY, never symlink, since Stage A mutates the input) — use the persistent runner cache /root/base-noble.qcow2. - workflow: BASE_IMAGE_URL from vars; smoke test continue-on-error (advisory until validated on a KVM runner); checkout the dispatched ref (not hardcoded dev) so the draft can be dry-run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): pin cryptpilot-fde to fix/srv-default-entry (saved_entry fallback), not the ESP-convert branch Dry-run caught it: CRYPTPILOT_REF=52a77a2 (fix/gcp-convert-esp-sync) has no show-reference-value fallback, so gen-reference-values errored 'saved_entry not found' on a never-booted image. The fallback lives on fix/srv-default-entry @ 64fe4e744574c8e0273dd1e5d54ceae9714748a0 (falls back to the default grub.cfg menuentry; also has --hash-algo). Pin to the full SHA. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gcp-cvm): migrate to cryptpilot 0.8.0 + #128/#130; simplify build (drop fix-B & keep-generic workarounds) Realizes what #130 was for: let cryptpilot-convert do the ESP-grub sync itself instead of our scripts. Validated end-to-end on GCP TDX hardware (boot → /data auto-provision → docker/sysbox → tapp-server). Toolchain (ci/setup-toolchain.sh, reproducible): install released cryptpilot 0.8.0 (crypt/verity/ fde-host + dracut) and overlay convert (#130) + fde-host (#128) built from the pinned fork commit 0gfoundation/cryptpilot@57d7512 (feat/0g-gcp-cvm = 0.8.0 + #128 + #130). In-image runtime = released 0.8.0 cryptpilot-fde-guest deb (fetch-inputs). Script simplification: - prepare-gcp-tapp.sh: DROP fix-B (convert #130 syncs the ESP grub.cfg + modules itself); add CRYPTPILOT_CONVERT override. - build-gcp-tapp.sh: FDE_PACKAGE -> 0.8.0 guest deb; config.toml gains max_log_files=7 (bounds /var/log/tapp on the RAM rootfs — issue #23). - ci/gen-reference-values.sh: use /usr/bin/cryptpilot-fde-host (from setup-toolchain). - ci/build-cryptpilot-fde.sh: removed (subsumed by setup-toolchain). - workflow: add "Set up 0.8.0 toolchain" step; ref-value step uses the installed fde-host. - add publish-gcp-image.sh (Stage C) to this branch. TODO (deferred): drop the leftover -generic kernel for a gcp-only image; wire tapp-server from dev in the real pipeline; re-verify prod (HARDEN=1) variant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ci): dual trigger — release auto-builds default owner; dispatch for custom owner - on: release (published) -> build the DEFAULT deployment (vars.DEFAULT_OWNER_ADDRESS), both env, publish + register. workflow_dispatch -> custom per-owner (owner input; blank = default). - resolve version/owner/publish for both triggers; matrix env defaults to both on release. - header comment updated to the 0.8.0 toolchain (ci/setup-toolchain.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ci): rename DEFAULT_OWNER_ADDRESS var -> OWNER_ADDRESS Consistent naming with the other vars.* config (no DEFAULT_ prefix); it still serves as the default/fallback owner for release builds and blank dispatch input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * harden(ci): keep GCP SA credential off the persistent runner Point gcloud/gsutil at a job-scoped CLOUDSDK_CONFIG under RUNNER_TEMP, write the key there only long enough to activate, delete it immediately, and revoke + wipe the config dir in cleanup. Nothing lands in the runner's ~/.config/gcloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): don't use runner.temp in job-level env (startup failure) runner context isn't available in jobs.<id>.env — GitHub rejected the workflow file. Set CLOUDSDK_CONFIG in the auth step via $RUNNER_TEMP and export it into $GITHUB_ENV for later steps; guard cleanup when it's unset (publish=false). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): install grpcurl in setup-toolchain (register-shared-as needs it) (#37) The reference-value registration step (verifier/register-shared-as.sh) calls grpcurl for SetAttestationPolicy, but grpcurl isn't in the al8 repos and wasn't provisioned, so publish=true runs failed at 'grpcurl: command not found'. Install the release binary to /usr/local/bin if missing. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(verifier): add cloud dimension to reference values + AS policy (#39) Each cloud builds its own image (GCP uses linux-image-gcp/gVNIC/GCP boot fixes; Ali uses its own kernel/drivers), so boot-chain measurements genuinely differ per cloud. Without a cloud dimension, clouds collide on the shared repo path and shared AS policy id. - path: verifier/reference-values/<cloud>/<version>/<env>/<owner>.json - policy: 0g-tapp-<cloud>-<version>-<env>-<owner> - gen-reference-values.sh / register-shared-as.sh / open-refvalues-pr.sh take <cloud> first - build-cvm.yml sets CLOUD=gcp (this kit is GCP-specific; an Ali pipeline sets ali) - README + register usage updated; also fixed stale build-cryptpilot-fde.sh reference Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): reference-values PR write perms + release auto-trigger (RELEASE_PAT) (#40) * fix(ci): grant contents/pull-requests write for the reference-values PR The Open-PR step pushes a refvalues/* branch and opens a PR into dev via GITHUB_TOKEN, but the default token is read-only -> 'Permission denied to github-actions[bot]' 403 on git push. Add a workflow permissions block granting contents:write + pull-requests:write. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): fix release auto-trigger + refvalues PR via RELEASE_PAT - build.yml: create the GitHub Release with RELEASE_PAT (falls back to GITHUB_TOKEN). Releases created by GITHUB_TOKEN do NOT fire on:release in other workflows (anti-recursion), so build-cvm's release auto-trigger never ran. A PAT-created release fires it. - build-cvm.yml: Open-PR step uses RELEASE_PAT for 'gh pr create' (covers orgs that disable Actions-created PRs); git push still uses GITHUB_TOKEN via the permissions block. Requires repo secret RELEASE_PAT (fine-grained PAT: Contents+Pull-requests write on 0g-tapp). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(main): CLI material compile fix + CI build speed (cherry-picks from dev) (#41) * fix(cli): update GetSecretResourceRequest for new material field PR #35 added the material field to GetSecretResourceRequest and updated the server, but tapp-cli's initializer was left behind, breaking cargo build --workspace (E0063) — this is what failed the release pipeline on v0.2.0-test. Expose it as an optional --material flag (default empty, unchanged behavior). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(ci): cache toolchain layers, use official rustup source, drop -j1 Release run 29242499831 took 2h23m: rustup via the USTC mirror crawled for 88 min on the US-based runner, yum via Aliyun OSS took 11 min, and CARGO_BUILD_JOBS=1 made the compile itself 13 min. - Split the Dockerfile into a toolchain stage (yum + rustup, no sources) and a builder stage; cache the toolchain stage in the GHA cache so it only rebuilds when the Dockerfile changes. - Default rustup to static.rust-lang.org; domestic mirrors stay available via --build-arg for builds from inside China. Mirror choice does not affect the installed bits. - Drop CARGO_BUILD_JOBS=1: job parallelism only affects scheduling, not codegen. Verified empirically — clean -j1 and -j4 builds with identical paths produce sha256-identical tapp-server and tapp-cli. Expected: ~5-8 min warm cache, ~15 min cold, vs 27 min baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(ci): include owner in the published GCP image name/family (avoid collision) (#44) The GCP image name was og-tdx[-dev]-<version> with no owner, so two owners building the same version×env would collide on the image name (publish refuses an existing name). owner is a measured dimension, so bake it into the name + family: og-tdx[-dev]-<version>-<owner(lowercased)>. Add a name length/pattern guard to publish-gcp-image.sh (name now carries the ~42-char owner; GCP image names are max 63 chars / [a-z][-a-z0-9]*). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Multi-cloud CVM build kit (GCP + Alibaba Cloud) (#46) * feat(cvm): parameterize build/prepare by CLOUD (gcp|ali); fix branch to ali for testing Integrate (not fork) Ali support by branching the few cloud-specific points on $CLOUD: - prepare-*.sh: gcp swaps in linux-image-gcp + fix A + verifies GCP ESP; ali keeps the base's generic kernel (Ali ECS boots generic via virtio) — skips kernel swap/fix A/ESP verify. convert / DNS / nbd are shared. GCP behavior unchanged (CLOUD defaults to gcp). - build-*.sh: export CLOUD to prepare (stage B); stage C picks publish-<cloud>-image.sh. - build-cvm.yml: CLOUD temporarily FIXED to 'ali' on this branch to test the Ali build path (per '固定走 aliyun 流程'); becomes an input/matrix before merging to main. Shared (unchanged, cloud-agnostic): tapp-server/config.toml/docker/sysbox//data/DNS/netplan + ci/gen-reference-values, ci/open-refvalues-pr, ci/setup-toolchain, register-shared-as. TODO: publish-ali-image.sh (OSS + aliyun ecs ImportImage); ali dev-variant guest agent (google-guest-agent is gcp-only); validate on a real Ali TDX instance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): ali dev variant relies on cloud-init for SSH/network (skip google-guest-agent) HARDEN=0 dev keeps cloud-init (only HARDEN=1 purges it); on Alibaba Cloud cloud-init injects the instance SSH key + configures networking from the Ali metadata service, so an ali dev image is SSH-able without the GCP-only google-guest-agent. GCP dev unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): ali uses UKI/systemd-boot (--uki) + pins cloud-init datasource to AliYun Per the Alibaba confidential-disk guide, Ali's cryptpilot flow is UKI/systemd-boot, not grub: - prepare (ali): keep generic kernel; install dracut/dracut-core/systemd-boot-efi (UKI prereqs); cryptpilot-convert gets --uki for ali (gcp stays grub + #130 ESP sync). No GCP ESP handling on ali. - build (ali dev): pin cloud-init datasource_list: [ AliYun ] so it reliably reads the Ali metadata service and injects the instance SSH key + network (Alibaba recommends pinning over ds-identify). GCP path unchanged (CLOUD=gcp branches untouched). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cvm): gpg --dearmor with --batch --no-tty (fails in tty-less env) The SGX/docker repo-key setup piped into 'gpg --dearmor' without --batch/--no-tty; in a fully detached (no controlling tty) environment gpg errors 'cannot open /dev/tty', aborting the guest provisioning. --batch --no-tty is the correct non-interactive form (identical dearmor output). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cvm): pre-install dracut-network for ali (cryptpilot-fde-guest dep) cryptpilot-fde-guest depends on dracut-network; pre-install it in the ali kernel step so the guest deb install during convert doesn't hit a missing-dep dpkg error (convert auto-resolves, but cleaner). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): gen-reference-values captures the tool's actual components (grub 5 / UKI 1) The reference-value component set is a property of the boot FORMAT, not the cloud: grub emits shim/grub/kernel/initrd/kernel_cmdline (5); UKI emits a single measurement.uki. The script hardcoded the 5 grub keys and failed on a UKI image. Make it format/cloud-agnostic: keep whatever non-empty measurement.*.SHA-384 keys show-reference-value emits (require >=1). Verified on a real UKI image (-> measurement.uki.SHA-384). cloud stays only the output-path dimension. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): add [server] unix_socket_path to config.toml + RuntimeDirectory=tapp tapp-server also serves gRPC on a Unix domain socket (/run/tapp/tapp.sock) that app containers bind-mount. Add the [server] unix_socket_path config, and RuntimeDirectory=tapp on the service so /run/tapp exists (0755, cleaned on stop) for tapp-server to bind it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(cvm): make BOOT_FORMAT (grub|uki) an independent build dimension Decouple boot format from cloud: CLOUD picks kernel/guest (gcp kernel+fixA vs generic), BOOT_FORMAT picks the boot format (grub vs uki -> --uki + UKI prereqs + ESP-verify gating). Default by cloud (gcp->grub, ali->uki) but overridable (e.g. a uki image on gcp). A CVM is built as exactly one CLOUD × one BOOT_FORMAT = one measurement chain (grub 5 components / uki 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(cvm): rename gcp-cvm -> cvm; build-gcp-tapp.sh -> build-tapp.sh; prepare-gcp-tapp.sh -> prepare-tapp.sh The kit is now multi-cloud (gcp + ali), so the gcp-specific names are misleading. Directory and the two shared-core scripts renamed; publish-gcp-image.sh keeps its name (GCP-specific; ali gets publish-ali-image.sh). All references updated (build-cvm.yml paths, script internals, docs). No behavior change — pure rename + path updates. cryptpilot-gcp-boot-fix.md keeps its name (GCP grub doc). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ci): cloud + boot_format as workflow dimensions (un-fix ali) - add workflow_dispatch inputs: cloud (gcp|ali, default gcp), boot_format (auto|grub|uki, default auto). - CLOUD = inputs.cloud || gcp; BOOT_FORMAT = auto -> '' (build-tapp derives per cloud). release -> gcp. - concurrency group keyed by cloud too. - gate 'Authenticate to GCP' + 'Stage C publish' on CLOUD==gcp (ali image publish via OSS+ImportImage is TODO); reference-values compute/register + Open-PR stay cloud-agnostic (run for ali too). Replaces the branch's temporary 'CLOUD: ali' pin — cloud is now a real dispatch dimension. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): add publish-ali-image.sh (OSS + aliyun ecs ImportImage) Ali Stage C per the confidential-disk flow: ossutil cp the qcow2 to OSS -> ImportImage (Architecture=x86_64, BootMode=UEFI, Format=QCOW2) -> enable NVMe (ModifyImageAttribute Features.NvmeSupport=supported, set after import is kicked off) -> wait until Available. The 4 easy-to-get-wrong params (64-bit / UEFI / QCOW2 / NVMe-after-import) are pinned. Requires aliyun + ossutil authenticated (ALIYUN_REGION + creds). DRAFT — not yet run end-to-end (needs Ali OSS/creds); build-tapp.sh Stage C already dispatches to it for CLOUD=ali. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cvm): NVMe via --Features.NvmeSupport supported --force (CLI schema gap) The aliyun CLI's ModifyImageAttribute schema doesn't expose the Features.NvmeSupport sub-field as a flag (rejects both --Features.NvmeSupport and JSON --Features 'flat format required'); --force skips client validation and sends the flat param the server wants. Validated end-to-end: og-tdx-ali-dev-v0-1-0 imported Available with BootMode=UEFI, Arch=x86_64, NvmeSupport=supported. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ci): wire ali publish into the workflow (EcsRamRole, no secret) The al8 build runner is an Ali ECS instance, so add: - 'Authenticate to Ali' (CLOUD==ali): aliyun configure --mode EcsRamRole (ossutil already uses the instance role) — no AK/SK secret needed. - 'Stage C (ali)' (CLOUD==ali): ./publish-ali-image.sh (OSS + ImportImage), image name carries owner. - ali config as vars: ALIYUN_REGION (ap-southeast-1), ALIYUN_ECS_ROLE (0g-confidential-disk-oss), OSS_BUCKET (0g-confidential-disk). gcp Stage C unchanged (owner-in-name handled by PR #44). Both clouds now publish from one workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(cvm): rewrite README for multi-cloud (gcp+ali) + dimension/platform tables Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(cvm): fix .gitignore paths after gcp-cvm->cvm rename; untrack fde debs The rename left .gitignore pointing at gcp-cvm/*, so the two binary FDE .debs under cvm/ got tracked. Retarget the patterns to cvm/ and drop the debs from git (they stay on disk; placed locally per README prerequisites). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop build-cvm on:release auto-trigger (dispatch-only) (#47) The release auto-trigger only ever built one slice (cloud=gcp + default owner) of the now multi-cloud/multi-owner matrix, and was the sole reason build.yml needed a repo-scoped RELEASE_PAT (GITHUB_TOKEN-created releases don't fire 'on: release'). Remove it: workflow_dispatch is the single entry point, and build.yml's release step goes back to the default GITHUB_TOKEN. The reference-values Open-PR step keeps its RELEASE_PAT || GITHUB_TOKEN fallback (separate use; falls back cleanly if the secret is removed). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * verifier: support UKI reference values in registration + policy (#48) The gcp+uki validation run failed at 'register on AS' with 'measurement.shim empty': register-shared-as.sh hard-required the 5 grub components, and policy.rego only verified the grub chain. UKI images carry a single measurement.uki instead. - policy.rego: add ref_uki + an all_bsa_digests helper + a second boot_chain_ok branch that matches the (specific, known) UKI digest against any boot-services application event. One branch per format; only the format whose ref set is populated fires, so an image verifies as exactly one (fail-closed otherwise). - register-shared-as.sh: inject a literal set per known rule (empty set() when the key is absent) instead of erroring on missing grub keys; require >=1 value overall. Validated with opa eval: grub positive=3 (no regression), uki positive=3, digest-mismatch/cross-format=33 (fail-closed both ways). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: default boot_format to grub for any cloud (uki opt-in) (#49) Drop the per-cloud auto derivation (gcp->grub, ali->uki). boot_format is an independent axis; 'auto' now resolves to grub regardless of cloud, and uki is an explicit opt-in (BOOT_FORMAT=uki / boot_format=uki), for any cloud. Updates build-tapp.sh, prepare-tapp.sh, the build-cvm.yml input description, and README. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop redundant boot_format 'auto' option, default grub directly (#50) 'auto' resolved to grub anyway (per #49), so it was identical to the 'grub' option. Make the input options [grub, uki] with default grub, and simplify the BOOT_FORMAT env to inputs.boot_format directly. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: add opt-in OVERWRITE for publish (delete+recreate existing image) (#51) Version images are immutable by default — a published image may back running confidential instances, and silently replacing it changes measurements and breaks their attestation. But re-running the same version during testing hit a hard 'already exists' failure and required a manual delete. Add OVERWRITE (env / dispatch input 'overwrite', default off): when the target image name already exists, delete-then-recreate instead of failing. Wired into both publish-gcp-image.sh (gcloud images delete) and publish-ali-image.sh (DeleteImage by resolved ImageId), and the build-cvm.yml Stage C steps. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: make boot_format a first-class identifier dimension (name/refval/policy) (#52) grub and uki produce different images with different measurement shapes (grub 5 components / uki 1), but boot_format wasn't in the image name, reference-value path, branch, or AS policy id — so a gcp+uki build clobbered the gcp+grub one (same name/path/PR #42/policy). Add boot_format everywhere cloud is, and drop the redundant 0x owner prefix globally so the longer names still fit GCP's 63-char cap: - image name: <imgbase>-<boot_format>-<version>-<owner40hex> (og-tdx-dev-grub-v0-2-0-<40hex> = 63) - refvalue: verifier/reference-values/<cloud>/<boot_format>/<version>/<env>/<owner>.json - policy id: 0g-tapp-<cloud>-<boot_format>-<version>-<env>-<owner> - branch: refvalues/<cloud>-<boot_format>-<version>-<env>-<owner> - concurrency group also keyed by boot_format owner is normalized identically in all scripts: strip 0x + lowercase. Updates gen-reference-values.sh, open-refvalues-pr.sh, register-shared-as.sh, build-cvm.yml, and both READMEs. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: add DISK_SIZE + image_name (custom output name) parameters (#54) DISK_SIZE (build-tapp.sh env / build-cvm dispatch input, default 20G): grow the working image up front (qemu-img resize + growpart /dev/sda 1 + resize2fs) before Stage A/convert, so the produced verity-rootfs image can be larger than the cached 20G base. Only grows (>= current is a no-op; shrinking a populated fs is unsafe). image_name (build-cvm dispatch input, default blank): override the published image name; blank keeps the auto <imgbase>-<boot_format>-<version>-<owner>. Applies to both gcp and ali Stage C (use a single env, not both, to avoid a name collision). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop the advisory boot smoke test step from build-cvm (#55) On this builder (no /dev/kvm) it runs under TCG, so the 600s window is truncated before multi-user (false 'BOOT NOT CONFIRMED' on good images) and it was advisory (continue-on-error) so it never gated — ~10min/build of noise. Keep cvm/test/boot-smoke-test.sh for local / KVM runs; revisit as a hard gate once a KVM-capable runner exists. Real boot + attestation validation is on TDX hardware. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: move image_name input to the top of the build-cvm dispatch form (#56) Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: runtime owner claim — unclaimed boot + measured ClaimOwner RPC - config: owner_address now optional; initial_whitelist removed (use the measured add-to-whitelist RPC after claiming) - server boots UNCLAIMED without an owner; first valid signer of ClaimOwner becomes owner, extended as a claim_owner runtime measurement event - claimed owner persisted under /run (tmpfs) so a process restart within the same boot cannot reopen the claim; config/persisted mismatch refuses to start - legacy baked-in owner still works and is now also measured at startup - cli: claim-owner command with end-to-end verification; examples/claim_owner.sh Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build/ci/docs: drop the per-owner image dimension (ownerless claim-at-runtime images) - build-tapp.sh: OWNER_ADDRESS now optional (legacy); default builds an ownerless image whose owner is claimed at runtime as a measured event - build-cvm.yml: remove the owner input/env and the owner tag from image names/family/concurrency; reference values register under the fixed 'any' owner slot (refvalues scripts unchanged) - docs: README claim-owner section, cvm/README dimensions, reference-values README, EVIDENCE_AND_AS_VERIFICATION claim_owner reconciliation rule - version: tapp-server 0.3.0 (new RPC), tapp-cli 0.3.0 (new command) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: ClaimConfig — extend claim to include chain+kbs runtime config Renames ClaimOwner → ClaimConfig and extends it to carry the full runtime configuration: chain_rpc_url, chain_contract_address, kbs_node_urls. - proto: ClaimConfigRequest has chain/kbs fields; both proto copies synced - server: measurement event is now claim_config and includes all fields; kms_client wrapped in Arc<RwLock> so dynamic-mode kbs_node_urls initialise the KMS client at claim time (no [kbs] section in config.toml needed) - establish_owner_at_startup: passes chain/kbs from config.toml into the claim_config measurement so pre-baked and dynamic modes produce the same event shape — verifiers see the complete runtime config in both modes - cli: claim-config command with --chain-rpc-url / --chain-contract / --kbs-urls - examples/claim_owner.sh: updated for ClaimConfig RPC - e2e: 14/14 with chain+kbs fields in the test claim Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * build: explicit canonical/custom build mode selection Replaces the implicit OWNER_ADDRESS-present-or-not heuristic with an explicit BUILD_MODE parameter (canonical | custom). canonical (default): owner-agnostic image — no owner baked in, one reference set for all owners (refvals under 'any' slot), image name has no owner tag. OWNER_ADDRESS is cleared even if accidentally set. custom: owner baked into config.toml — per-owner initrd measurement, per-owner refval set and image name. Requires OWNER_ADDRESS. build-tapp.sh: BUILD_MODE env var; custom mode enforces OWNER_ADDRESS. build-cvm.yml: build_mode input (choice); owner input (custom only); names step resolves img_suffix + owner_slot per mode; both CI paths (canonical and custom) fully supported. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * build: canonical refvals at <env>.json (no owner subdir/placeholder) Drop the 'any' placeholder — canonical mode uses a clean path without an owner dimension: canonical: verifier/reference-values/<cloud>/<boot_format>/<version>/<env>.json policy id: 0g-tapp-<cloud>-<boot_format>-<version>-<env> custom: verifier/reference-values/<cloud>/<boot_format>/<version>/<env>/<owner>.json policy id: 0g-tapp-<cloud>-<boot_format>-<version>-<env>-<owner> gen-reference-values.sh / register-shared-as.sh / open-refvalues-pr.sh: owner arg is now optional; omit for canonical, pass 0x address for custom. register-shared-as.sh auto-detects owner vs AS-endpoint for arg 5. build-cvm.yml: owner_slot replaced by owner_tag (empty = canonical); scripts called with bare $OWNER_TAG so empty expands to nothing (not an empty string arg). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: require tapp-server >= v0.3.0 for canonical build mode ClaimConfig RPC was introduced in 0.3.0; a canonical image built from an older binary boots unclaimed with no way to claim it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: fail-fast input validation + fix custom-mode owner passing - validate step moved to the very first step (before toolchain/download): canonical requires >= v0.3.0; custom requires an explicit well-formed 0x owner (vars fallback removed — a silently-defaulted owner baked into a measured image is how mistakes ship); image_name+env=both rejected (matrix jobs would clobber each other); release binary existence probed - fix: refvalue steps passed the 0x-stripped owner_tag, but register-shared-as.sh detects owner-vs-endpoint by the 0x prefix — custom mode would have treated the owner as the AS endpoint. Now the raw 0x address is passed everywhere (scripts strip it themselves) and the detection regex also accepts unprefixed 40-hex - OWNER env is empty unless build_mode=custom (canonical steps can never see an owner); BUILD_MODE forwarded to build-tapp.sh explicitly - docs: drop remaining 'any'-slot references; canonical/custom identifier tables; claim_config event shape in EVIDENCE_AND_AS_VERIFICATION.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build: KBS_URLS optional in canonical mode canonical images can receive kbs_node_urls at runtime via ClaimConfig; custom mode still requires KBS_URLS to be baked in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: fix KBS_URLS passing (inline quoted assignment) + normalize literal [] - $KBS_ARG expanded from a variable is not parsed as an env assignment by the shell — with vars.TAPP_KBS_URLS='[]' the word KBS_URLS=[] became the command name ('command not found'). Keep the assignment inline and quoted. - build-tapp.sh: treat a literal [] as empty (would interpolate into node_urls = [[]], broken TOML). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: fix env passing to build-tapp.sh — no $VAR in assignment-prefix list bash decides what is an assignment prefix BEFORE expansion: a variable ($EXTRA/$KBS_ARG, even when empty) in the prefix list turns every later VAR=val word into the command name — the actual cause of both 'KBS_URLS=[]: command not found' failures. BUILD_MODE/KBS_URLS are already inherited from the job env; custom mode exports OWNER_ADDRESS explicitly. Verified locally with a simulated step for both modes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * skill: update 0g-tapp-cli v1.4.0 — claim-config, unclaimed state, new policy id format Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * cvm: RuntimeDirectoryPreserve=yes — keep claimed_owner across process restarts systemd removes RuntimeDirectory on stop by default; restart = stop+start, so /run/tapp/claimed_owner was wiped on every systemctl restart, allowing re-claim. Preserve=yes keeps the dir across restarts but still clears it on VM reboot (tmpfs) — matching the RTMR lifetime. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: get-tapp-info shows runtime chain + kbs from ClaimConfig - proto: add ChainConfigInfo message and chain field to TappConfigInfo - server: ClaimedRuntimeConfig stores chain/kbs set at claim time; get_tapp_info reads runtime values, falls back to static config - cli: display Chain and KBS sections in get-tapp-info output - e2e: 14/14 still passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Wei <wei@0g.ai>
Wilbert957
added a commit
that referenced
this pull request
Jul 20, 2026
…ages, verify-app owner reconcile (#66) * feat: runtime CLI↔server/contract version-compatibility check (#26) * feat: runtime CLI↔server/contract version-compatibility check Implements the compatibility mechanism from docs/VERSIONING.md. Rust (gRPC edge): - tapp-common::compat::interface_warning compares MAJOR.MINOR (PATCH ignored), returning a warning when the peer's interface looks incompatible. - tapp-cli build.rs stamps the expected server interface version (the workspace-root tapp-server MAJOR.MINOR) at build time — no number to maintain. - create_client does a best-effort GetTappInfo on connect and warns to stderr on mismatch; it never hard-blocks (a genuinely missing RPC fails at the call). - Bump tapp-cli 0.1.0 -> 0.1.1 (adds the on-connect warning behavior). Contract (on-chain edge): - Add a version() view to TappRegistry returning "0.1.0" so callers can read the live implementation version behind the stable beacon proxy address. - Rename local `version` vars to `ackVersion` to avoid shadowing the new view. Server needs no change: GetTappInfo already reports the full tapp-server version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(contract): record TappRegistry v0.1.0 upgrade (adds version()) Deployed new implementation 0x9Ea52Ef383e8eA3fe7F0890309D3C62b2FC1Ac2B behind the stable beacon proxy 0x2Ce8...165c9 on 0G Galileo testnet; beacon.upgradeTo tx 0x8a003a1a. version() now returns "0.1.0" on-chain, storage preserved, source-verified on chainscan-galileo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync Cargo.lock (tempfile dev-dependency) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gcp-cvm): add Stage C (publish to GCP) + document the 3-stage pipeline (#29) The build produced a qcow2 but stopped short of a GCP image. Add the "last mile": - publish-gcp-image.sh (Stage C): qemu-img convert qcow2 -> raw -> sparse oldgnu tar.gz -> gsutil cp -> gcloud compute images create with UEFI_COMPATIBLE,GVNIC,SEV_CAPABLE,TDX_CAPABLE. Parameterized (GCS_BUCKET/GCP_PROJECT/GUEST_OS_FEATURES, defaults gs://tapp-image + g-devops); refuses to clobber an existing image name. Kept separate from the build because it needs gcloud/gsutil auth + network upload. - build-gcp-tapp.sh: opt-in Stage C via PUBLISH_AS=<image-name> (runs publish after Stage B); also fix the now-stale tapp-data-provision.service Description (format-or-adopt, not "carve"). - Docs: README pipeline overview (Stage 0 base prep / A / B / optional smoke test / C), publish-gcp-image.sh in the file table, PUBLISH_AS env var, a "Publish to GCP (Stage C)" section; main doc §14 (Stage C steps + stages-at-a-glance). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: start-app --register-onchain — idempotent on-chain registration before the app starts (#28) * feat: start-app --register-onchain registers the app before it starts New optional flow for 'start-app': with --register-onchain (+ --rpc-url, --contract, --stake-wei), the CLI idempotently ensures the app is registered on-chain BEFORE its containers start: - app not registered -> registerApp (this node = first node) - registered, signer not a node -> addNode (per-node override iff differs) - signer already in node list -> skip, just start To get real hashes before anything runs, StartAppRequest gains a measure_only flag: the server stages files, 'docker compose pull's the images and computes compose/volumes/image hashes without starting containers, returning them via new TaskResult fields. The follow-up normal StartApp starts from the cached images. GetAppKey now creates the app key if missing (same as GetEvidence), so the signer address is available before the app starts; the key lives in server memory so the app gets the same key when it runs. e2e-tested against 0G Galileo testnet (registerApp + idempotent skip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document start-app --register-onchain in README, runbook, and skill Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix: make start-service idempotent, dropping the already-running pre-check (#30) start-service failed forever with 'Service <svc> is already running' after stop-service, whenever a running sibling service's name contained the stopped one (e.g. sandbox vs sandbox-redis): the pre-check did a substring match on docker compose ps output. (#27) Rather than fixing the match, drop the pre-check: docker compose up -d is already idempotent (running + unchanged config = no-op), and removing it also unblocks 'start-service --pull' on a running service, whose re-measure path existed downstream but was unreachable. Typo'd service names still fail the task with docker's own 'no such service' error. e2e-verified: restart-after-stop with prefix sibling running, no-op on running service, --pull on running service, and bad-name failure. Fixes #27 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * chore: bump tapp-server and tapp-cli to 0.2.0 Per docs/VERSIONING.md, for the changes merged to dev: - tapp-server 0.1.1 -> 0.2.0: gRPC interface changed (#28 adds StartAppRequest.measure_only + TaskResult measurement fields; #30 changes StartService semantics for already-running services) - tapp-cli 0.1.1 -> 0.2.0: new user-facing flags (#28: --register-onchain / --rpc-url / --contract / --stake-wei on start-app) - contract unchanged (0.1.0); tapp-common internal, additive only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(logging): bound file-log growth + move CVM logs off the RAM rootfs (#34) * fix(logging): bound file-log growth + move CVM logs off the RAM rootfs (#23) - RollingFileAppender now keeps at most logging.max_log_files daily files (new config field, default 7) — daily rotation alone never deletes old files, so total size was unbounded - gcp-cvm build: tapp-server file logs go to /data/log/tapp/ (persistent disk) instead of /var/log/tapp/ on the RAM-backed rootfs overlay, and tapp-server.service gains RequiresMountsFor=/data (same fail-loud policy as docker/containerd) Closes #23 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(logging): prune stale daily logs at startup, not only at in-process rotation tracing-appender's max_log_files only prunes inside refresh_writer (when the date rolls over while the process runs) — verified empirically: files left by previous runs survive until the first in-process midnight, and a process that never lives past midnight never prunes. Add a best-effort startup prune of {prefix}.* files before building the appender, with a unit test. Also fix the stale KmsClient::new arity in kms_client tests (predates the retry-config parameter; lib tests did not compile). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(server): pass derivation material through GetSecretResource to KMS /app-key (#35) - proto: GetSecretResourceRequest gains optional 'string material = 2' — hex-encoded derivation material, opaque to tapp, forwarded verbatim - kms_client: KmsRequest carries material; omitted from the JSON body when empty so the request stays byte-identical to the pre-material format (KMS derives purely from app_id, existing callers untouched) - tests: new wiremock test asserting material is forwarded verbatim and omitted when empty; also fix stale KmsClient::new arity in existing tests Closes #33 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): document max_log_files + GetSecretResource material; fix stale config sample - [logging] sample: path -> file_path (actual config key), add format and the new max_log_files retention cap (#34) - config sample and KMS section: [kms] cluster_urls -> [kbs] node_urls (the section/key tapp-server actually reads), port 8080 -> 9091 - KMS Integration: document the optional derivation material passthrough (#35) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): update GetSecretResourceRequest for new material field PR #35 added the material field to GetSecretResourceRequest and updated the server, but tapp-cli's initializer was left behind, breaking cargo build --workspace (E0063) — this is what failed the release pipeline on v0.2.0-test. Expose it as an optional --material flag (default empty, unchanged behavior). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(ci): cache toolchain layers, use official rustup source, drop -j1 Release run 29242499831 took 2h23m: rustup via the USTC mirror crawled for 88 min on the US-based runner, yum via Aliyun OSS took 11 min, and CARGO_BUILD_JOBS=1 made the compile itself 13 min. - Split the Dockerfile into a toolchain stage (yum + rustup, no sources) and a builder stage; cache the toolchain stage in the GHA cache so it only rebuilds when the Dockerfile changes. - Default rustup to static.rust-lang.org; domestic mirrors stay available via --build-arg for builds from inside China. Mirror choice does not affect the installed bits. - Drop CARGO_BUILD_JOBS=1: job parallelism only affects scheduling, not codegen. Verified empirically — clean -j1 and -j4 builds with identical paths produce sha256-identical tapp-server and tapp-cli. Expected: ~5-8 min warm cache, ~15 min cold, vs 27 min baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(verifier): reference values for gcp/uki/v0.2.0/dev/ea695c312ce119de347425b29aff85371c9d1837 (#53) Co-authored-by: cvm-ci <cvm-ci@0g.ai> * feat: runtime owner claim — ownerless CVM images, measured ClaimOwner RPC (#58) * feat: runtime CLI↔server/contract version-compatibility check (#26) * feat: runtime CLI↔server/contract version-compatibility check Implements the compatibility mechanism from docs/VERSIONING.md. Rust (gRPC edge): - tapp-common::compat::interface_warning compares MAJOR.MINOR (PATCH ignored), returning a warning when the peer's interface looks incompatible. - tapp-cli build.rs stamps the expected server interface version (the workspace-root tapp-server MAJOR.MINOR) at build time — no number to maintain. - create_client does a best-effort GetTappInfo on connect and warns to stderr on mismatch; it never hard-blocks (a genuinely missing RPC fails at the call). - Bump tapp-cli 0.1.0 -> 0.1.1 (adds the on-connect warning behavior). Contract (on-chain edge): - Add a version() view to TappRegistry returning "0.1.0" so callers can read the live implementation version behind the stable beacon proxy address. - Rename local `version` vars to `ackVersion` to avoid shadowing the new view. Server needs no change: GetTappInfo already reports the full tapp-server version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(contract): record TappRegistry v0.1.0 upgrade (adds version()) Deployed new implementation 0x9Ea52Ef383e8eA3fe7F0890309D3C62b2FC1Ac2B behind the stable beacon proxy 0x2Ce8...165c9 on 0G Galileo testnet; beacon.upgradeTo tx 0x8a003a1a. version() now returns "0.1.0" on-chain, storage preserved, source-verified on chainscan-galileo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync Cargo.lock (tempfile dev-dependency) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gcp-cvm): add Stage C (publish to GCP) + document the 3-stage pipeline (#29) The build produced a qcow2 but stopped short of a GCP image. Add the "last mile": - publish-gcp-image.sh (Stage C): qemu-img convert qcow2 -> raw -> sparse oldgnu tar.gz -> gsutil cp -> gcloud compute images create with UEFI_COMPATIBLE,GVNIC,SEV_CAPABLE,TDX_CAPABLE. Parameterized (GCS_BUCKET/GCP_PROJECT/GUEST_OS_FEATURES, defaults gs://tapp-image + g-devops); refuses to clobber an existing image name. Kept separate from the build because it needs gcloud/gsutil auth + network upload. - build-gcp-tapp.sh: opt-in Stage C via PUBLISH_AS=<image-name> (runs publish after Stage B); also fix the now-stale tapp-data-provision.service Description (format-or-adopt, not "carve"). - Docs: README pipeline overview (Stage 0 base prep / A / B / optional smoke test / C), publish-gcp-image.sh in the file table, PUBLISH_AS env var, a "Publish to GCP (Stage C)" section; main doc §14 (Stage C steps + stages-at-a-glance). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: start-app --register-onchain — idempotent on-chain registration before the app starts (#28) * feat: start-app --register-onchain registers the app before it starts New optional flow for 'start-app': with --register-onchain (+ --rpc-url, --contract, --stake-wei), the CLI idempotently ensures the app is registered on-chain BEFORE its containers start: - app not registered -> registerApp (this node = first node) - registered, signer not a node -> addNode (per-node override iff differs) - signer already in node list -> skip, just start To get real hashes before anything runs, StartAppRequest gains a measure_only flag: the server stages files, 'docker compose pull's the images and computes compose/volumes/image hashes without starting containers, returning them via new TaskResult fields. The follow-up normal StartApp starts from the cached images. GetAppKey now creates the app key if missing (same as GetEvidence), so the signer address is available before the app starts; the key lives in server memory so the app gets the same key when it runs. e2e-tested against 0G Galileo testnet (registerApp + idempotent skip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document start-app --register-onchain in README, runbook, and skill Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix: make start-service idempotent, dropping the already-running pre-check (#30) start-service failed forever with 'Service <svc> is already running' after stop-service, whenever a running sibling service's name contained the stopped one (e.g. sandbox vs sandbox-redis): the pre-check did a substring match on docker compose ps output. (#27) Rather than fixing the match, drop the pre-check: docker compose up -d is already idempotent (running + unchanged config = no-op), and removing it also unblocks 'start-service --pull' on a running service, whose re-measure path existed downstream but was unreachable. Typo'd service names still fail the task with docker's own 'no such service' error. e2e-verified: restart-after-stop with prefix sibling running, no-op on running service, --pull on running service, and bad-name failure. Fixes #27 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * chore: bump tapp-server and tapp-cli to 0.2.0 Per docs/VERSIONING.md, for the changes merged to dev: - tapp-server 0.1.1 -> 0.2.0: gRPC interface changed (#28 adds StartAppRequest.measure_only + TaskResult measurement fields; #30 changes StartService semantics for already-running services) - tapp-cli 0.1.1 -> 0.2.0: new user-facing flags (#28: --register-onchain / --rpc-url / --contract / --stake-wei on start-app) - contract unchanged (0.1.0); tapp-common internal, additive only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(logging): bound file-log growth + move CVM logs off the RAM rootfs (#34) * fix(logging): bound file-log growth + move CVM logs off the RAM rootfs (#23) - RollingFileAppender now keeps at most logging.max_log_files daily files (new config field, default 7) — daily rotation alone never deletes old files, so total size was unbounded - gcp-cvm build: tapp-server file logs go to /data/log/tapp/ (persistent disk) instead of /var/log/tapp/ on the RAM-backed rootfs overlay, and tapp-server.service gains RequiresMountsFor=/data (same fail-loud policy as docker/containerd) Closes #23 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(logging): prune stale daily logs at startup, not only at in-process rotation tracing-appender's max_log_files only prunes inside refresh_writer (when the date rolls over while the process runs) — verified empirically: files left by previous runs survive until the first in-process midnight, and a process that never lives past midnight never prunes. Add a best-effort startup prune of {prefix}.* files before building the appender, with a unit test. Also fix the stale KmsClient::new arity in kms_client tests (predates the retry-config parameter; lib tests did not compile). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(server): pass derivation material through GetSecretResource to KMS /app-key (#35) - proto: GetSecretResourceRequest gains optional 'string material = 2' — hex-encoded derivation material, opaque to tapp, forwarded verbatim - kms_client: KmsRequest carries material; omitted from the JSON body when empty so the request stays byte-identical to the pre-material format (KMS derives purely from app_id, existing callers untouched) - tests: new wiremock test asserting material is forwarded verbatim and omitted when empty; also fix stale KmsClient::new arity in existing tests Closes #33 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): document max_log_files + GetSecretResource material; fix stale config sample - [logging] sample: path -> file_path (actual config key), add format and the new max_log_files retention cap (#34) - config sample and KMS section: [kms] cluster_urls -> [kbs] node_urls (the section/key tapp-server actually reads), port 8080 -> 9091 - KMS Integration: document the optional derivation material passthrough (#35) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): automate og-tdx CVM build → publish → attest (version×env×owner) (#32) * draft(ci): automate og-tdx CVM build/publish/attest (version×env×owner) DRAFT for review — not wired to a runner yet. - .github/workflows/build-cvm.yml: dispatch(version,env,owner,publish) on a self-hosted al8 runner → build (HARDEN per env, OWNER baked, KBS fixed) → smoke gate → publish versioned image+family → compute+register reference values → PR them into dev. - verifier/register-shared-as.sh: add the owner dimension (path <version>/<env>/<owner>.json, policy id 0g-tapp-<version>-<env>-<owner>) — owner is measured (folded into initrd). - gcp-cvm/ci/{gen-reference-values,fetch-inputs,open-refvalues-pr}.sh helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): build the #128-fixed cryptpilot-fde from pinned fork source; real ref-value generator - gcp-cvm/ci/build-cryptpilot-fde.sh: compile cryptpilot-fde-host from the 0gfoundation cryptpilot fork at a pinned commit (transparent/reproducible vs a prebuilt blob). The stock 0.7.0 errors "saved_entry not found" on a never-booted image; the fork falls back to the default grub.cfg menuentry. - gcp-cvm/ci/gen-reference-values.sh: rewritten to use the fixed binary's real JSON output (filter to the five measurement.<component>.SHA-384 keys via python; fail if any is empty). Validated end-to-end against og-tdx-dev.qcow2. - build-cvm.yml: build the fixed cryptpilot-fde as a step, pass it to the ref-value step. - reference-values/README.md: owner dimension (<version>/<env>/<owner>.json) + fork-build flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): move fixed config to repo Actions Variables (KBS/project/bucket/AS/fde-ref) All tunables now come from repo/org Variables (vars.*) with in-file defaults, so KBS URLs, GCP project/bucket, AS endpoint and the cryptpilot-fde fork commit can be changed in GitHub settings without editing the workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): local base-image cache + non-blocking smoke test + checkout dispatched ref - fetch-inputs.sh: support BASE_IMAGE_URL as file:// or absolute local path (COPY, never symlink, since Stage A mutates the input) — use the persistent runner cache /root/base-noble.qcow2. - workflow: BASE_IMAGE_URL from vars; smoke test continue-on-error (advisory until validated on a KVM runner); checkout the dispatched ref (not hardcoded dev) so the draft can be dry-run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * draft(ci): pin cryptpilot-fde to fix/srv-default-entry (saved_entry fallback), not the ESP-convert branch Dry-run caught it: CRYPTPILOT_REF=52a77a2 (fix/gcp-convert-esp-sync) has no show-reference-value fallback, so gen-reference-values errored 'saved_entry not found' on a never-booted image. The fallback lives on fix/srv-default-entry @ 64fe4e744574c8e0273dd1e5d54ceae9714748a0 (falls back to the default grub.cfg menuentry; also has --hash-algo). Pin to the full SHA. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gcp-cvm): migrate to cryptpilot 0.8.0 + #128/#130; simplify build (drop fix-B & keep-generic workarounds) Realizes what #130 was for: let cryptpilot-convert do the ESP-grub sync itself instead of our scripts. Validated end-to-end on GCP TDX hardware (boot → /data auto-provision → docker/sysbox → tapp-server). Toolchain (ci/setup-toolchain.sh, reproducible): install released cryptpilot 0.8.0 (crypt/verity/ fde-host + dracut) and overlay convert (#130) + fde-host (#128) built from the pinned fork commit 0gfoundation/cryptpilot@57d7512 (feat/0g-gcp-cvm = 0.8.0 + #128 + #130). In-image runtime = released 0.8.0 cryptpilot-fde-guest deb (fetch-inputs). Script simplification: - prepare-gcp-tapp.sh: DROP fix-B (convert #130 syncs the ESP grub.cfg + modules itself); add CRYPTPILOT_CONVERT override. - build-gcp-tapp.sh: FDE_PACKAGE -> 0.8.0 guest deb; config.toml gains max_log_files=7 (bounds /var/log/tapp on the RAM rootfs — issue #23). - ci/gen-reference-values.sh: use /usr/bin/cryptpilot-fde-host (from setup-toolchain). - ci/build-cryptpilot-fde.sh: removed (subsumed by setup-toolchain). - workflow: add "Set up 0.8.0 toolchain" step; ref-value step uses the installed fde-host. - add publish-gcp-image.sh (Stage C) to this branch. TODO (deferred): drop the leftover -generic kernel for a gcp-only image; wire tapp-server from dev in the real pipeline; re-verify prod (HARDEN=1) variant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ci): dual trigger — release auto-builds default owner; dispatch for custom owner - on: release (published) -> build the DEFAULT deployment (vars.DEFAULT_OWNER_ADDRESS), both env, publish + register. workflow_dispatch -> custom per-owner (owner input; blank = default). - resolve version/owner/publish for both triggers; matrix env defaults to both on release. - header comment updated to the 0.8.0 toolchain (ci/setup-toolchain.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ci): rename DEFAULT_OWNER_ADDRESS var -> OWNER_ADDRESS Consistent naming with the other vars.* config (no DEFAULT_ prefix); it still serves as the default/fallback owner for release builds and blank dispatch input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * harden(ci): keep GCP SA credential off the persistent runner Point gcloud/gsutil at a job-scoped CLOUDSDK_CONFIG under RUNNER_TEMP, write the key there only long enough to activate, delete it immediately, and revoke + wipe the config dir in cleanup. Nothing lands in the runner's ~/.config/gcloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): don't use runner.temp in job-level env (startup failure) runner context isn't available in jobs.<id>.env — GitHub rejected the workflow file. Set CLOUDSDK_CONFIG in the auth step via $RUNNER_TEMP and export it into $GITHUB_ENV for later steps; guard cleanup when it's unset (publish=false). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): install grpcurl in setup-toolchain (register-shared-as needs it) (#37) The reference-value registration step (verifier/register-shared-as.sh) calls grpcurl for SetAttestationPolicy, but grpcurl isn't in the al8 repos and wasn't provisioned, so publish=true runs failed at 'grpcurl: command not found'. Install the release binary to /usr/local/bin if missing. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(verifier): add cloud dimension to reference values + AS policy (#39) Each cloud builds its own image (GCP uses linux-image-gcp/gVNIC/GCP boot fixes; Ali uses its own kernel/drivers), so boot-chain measurements genuinely differ per cloud. Without a cloud dimension, clouds collide on the shared repo path and shared AS policy id. - path: verifier/reference-values/<cloud>/<version>/<env>/<owner>.json - policy: 0g-tapp-<cloud>-<version>-<env>-<owner> - gen-reference-values.sh / register-shared-as.sh / open-refvalues-pr.sh take <cloud> first - build-cvm.yml sets CLOUD=gcp (this kit is GCP-specific; an Ali pipeline sets ali) - README + register usage updated; also fixed stale build-cryptpilot-fde.sh reference Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): reference-values PR write perms + release auto-trigger (RELEASE_PAT) (#40) * fix(ci): grant contents/pull-requests write for the reference-values PR The Open-PR step pushes a refvalues/* branch and opens a PR into dev via GITHUB_TOKEN, but the default token is read-only -> 'Permission denied to github-actions[bot]' 403 on git push. Add a workflow permissions block granting contents:write + pull-requests:write. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): fix release auto-trigger + refvalues PR via RELEASE_PAT - build.yml: create the GitHub Release with RELEASE_PAT (falls back to GITHUB_TOKEN). Releases created by GITHUB_TOKEN do NOT fire on:release in other workflows (anti-recursion), so build-cvm's release auto-trigger never ran. A PAT-created release fires it. - build-cvm.yml: Open-PR step uses RELEASE_PAT for 'gh pr create' (covers orgs that disable Actions-created PRs); git push still uses GITHUB_TOKEN via the permissions block. Requires repo secret RELEASE_PAT (fine-grained PAT: Contents+Pull-requests write on 0g-tapp). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(main): CLI material compile fix + CI build speed (cherry-picks from dev) (#41) * fix(cli): update GetSecretResourceRequest for new material field PR #35 added the material field to GetSecretResourceRequest and updated the server, but tapp-cli's initializer was left behind, breaking cargo build --workspace (E0063) — this is what failed the release pipeline on v0.2.0-test. Expose it as an optional --material flag (default empty, unchanged behavior). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(ci): cache toolchain layers, use official rustup source, drop -j1 Release run 29242499831 took 2h23m: rustup via the USTC mirror crawled for 88 min on the US-based runner, yum via Aliyun OSS took 11 min, and CARGO_BUILD_JOBS=1 made the compile itself 13 min. - Split the Dockerfile into a toolchain stage (yum + rustup, no sources) and a builder stage; cache the toolchain stage in the GHA cache so it only rebuilds when the Dockerfile changes. - Default rustup to static.rust-lang.org; domestic mirrors stay available via --build-arg for builds from inside China. Mirror choice does not affect the installed bits. - Drop CARGO_BUILD_JOBS=1: job parallelism only affects scheduling, not codegen. Verified empirically — clean -j1 and -j4 builds with identical paths produce sha256-identical tapp-server and tapp-cli. Expected: ~5-8 min warm cache, ~15 min cold, vs 27 min baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(ci): include owner in the published GCP image name/family (avoid collision) (#44) The GCP image name was og-tdx[-dev]-<version> with no owner, so two owners building the same version×env would collide on the image name (publish refuses an existing name). owner is a measured dimension, so bake it into the name + family: og-tdx[-dev]-<version>-<owner(lowercased)>. Add a name length/pattern guard to publish-gcp-image.sh (name now carries the ~42-char owner; GCP image names are max 63 chars / [a-z][-a-z0-9]*). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Multi-cloud CVM build kit (GCP + Alibaba Cloud) (#46) * feat(cvm): parameterize build/prepare by CLOUD (gcp|ali); fix branch to ali for testing Integrate (not fork) Ali support by branching the few cloud-specific points on $CLOUD: - prepare-*.sh: gcp swaps in linux-image-gcp + fix A + verifies GCP ESP; ali keeps the base's generic kernel (Ali ECS boots generic via virtio) — skips kernel swap/fix A/ESP verify. convert / DNS / nbd are shared. GCP behavior unchanged (CLOUD defaults to gcp). - build-*.sh: export CLOUD to prepare (stage B); stage C picks publish-<cloud>-image.sh. - build-cvm.yml: CLOUD temporarily FIXED to 'ali' on this branch to test the Ali build path (per '固定走 aliyun 流程'); becomes an input/matrix before merging to main. Shared (unchanged, cloud-agnostic): tapp-server/config.toml/docker/sysbox//data/DNS/netplan + ci/gen-reference-values, ci/open-refvalues-pr, ci/setup-toolchain, register-shared-as. TODO: publish-ali-image.sh (OSS + aliyun ecs ImportImage); ali dev-variant guest agent (google-guest-agent is gcp-only); validate on a real Ali TDX instance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): ali dev variant relies on cloud-init for SSH/network (skip google-guest-agent) HARDEN=0 dev keeps cloud-init (only HARDEN=1 purges it); on Alibaba Cloud cloud-init injects the instance SSH key + configures networking from the Ali metadata service, so an ali dev image is SSH-able without the GCP-only google-guest-agent. GCP dev unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): ali uses UKI/systemd-boot (--uki) + pins cloud-init datasource to AliYun Per the Alibaba confidential-disk guide, Ali's cryptpilot flow is UKI/systemd-boot, not grub: - prepare (ali): keep generic kernel; install dracut/dracut-core/systemd-boot-efi (UKI prereqs); cryptpilot-convert gets --uki for ali (gcp stays grub + #130 ESP sync). No GCP ESP handling on ali. - build (ali dev): pin cloud-init datasource_list: [ AliYun ] so it reliably reads the Ali metadata service and injects the instance SSH key + network (Alibaba recommends pinning over ds-identify). GCP path unchanged (CLOUD=gcp branches untouched). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cvm): gpg --dearmor with --batch --no-tty (fails in tty-less env) The SGX/docker repo-key setup piped into 'gpg --dearmor' without --batch/--no-tty; in a fully detached (no controlling tty) environment gpg errors 'cannot open /dev/tty', aborting the guest provisioning. --batch --no-tty is the correct non-interactive form (identical dearmor output). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cvm): pre-install dracut-network for ali (cryptpilot-fde-guest dep) cryptpilot-fde-guest depends on dracut-network; pre-install it in the ali kernel step so the guest deb install during convert doesn't hit a missing-dep dpkg error (convert auto-resolves, but cleaner). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): gen-reference-values captures the tool's actual components (grub 5 / UKI 1) The reference-value component set is a property of the boot FORMAT, not the cloud: grub emits shim/grub/kernel/initrd/kernel_cmdline (5); UKI emits a single measurement.uki. The script hardcoded the 5 grub keys and failed on a UKI image. Make it format/cloud-agnostic: keep whatever non-empty measurement.*.SHA-384 keys show-reference-value emits (require >=1). Verified on a real UKI image (-> measurement.uki.SHA-384). cloud stays only the output-path dimension. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): add [server] unix_socket_path to config.toml + RuntimeDirectory=tapp tapp-server also serves gRPC on a Unix domain socket (/run/tapp/tapp.sock) that app containers bind-mount. Add the [server] unix_socket_path config, and RuntimeDirectory=tapp on the service so /run/tapp exists (0755, cleaned on stop) for tapp-server to bind it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(cvm): make BOOT_FORMAT (grub|uki) an independent build dimension Decouple boot format from cloud: CLOUD picks kernel/guest (gcp kernel+fixA vs generic), BOOT_FORMAT picks the boot format (grub vs uki -> --uki + UKI prereqs + ESP-verify gating). Default by cloud (gcp->grub, ali->uki) but overridable (e.g. a uki image on gcp). A CVM is built as exactly one CLOUD × one BOOT_FORMAT = one measurement chain (grub 5 components / uki 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(cvm): rename gcp-cvm -> cvm; build-gcp-tapp.sh -> build-tapp.sh; prepare-gcp-tapp.sh -> prepare-tapp.sh The kit is now multi-cloud (gcp + ali), so the gcp-specific names are misleading. Directory and the two shared-core scripts renamed; publish-gcp-image.sh keeps its name (GCP-specific; ali gets publish-ali-image.sh). All references updated (build-cvm.yml paths, script internals, docs). No behavior change — pure rename + path updates. cryptpilot-gcp-boot-fix.md keeps its name (GCP grub doc). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ci): cloud + boot_format as workflow dimensions (un-fix ali) - add workflow_dispatch inputs: cloud (gcp|ali, default gcp), boot_format (auto|grub|uki, default auto). - CLOUD = inputs.cloud || gcp; BOOT_FORMAT = auto -> '' (build-tapp derives per cloud). release -> gcp. - concurrency group keyed by cloud too. - gate 'Authenticate to GCP' + 'Stage C publish' on CLOUD==gcp (ali image publish via OSS+ImportImage is TODO); reference-values compute/register + Open-PR stay cloud-agnostic (run for ali too). Replaces the branch's temporary 'CLOUD: ali' pin — cloud is now a real dispatch dimension. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cvm): add publish-ali-image.sh (OSS + aliyun ecs ImportImage) Ali Stage C per the confidential-disk flow: ossutil cp the qcow2 to OSS -> ImportImage (Architecture=x86_64, BootMode=UEFI, Format=QCOW2) -> enable NVMe (ModifyImageAttribute Features.NvmeSupport=supported, set after import is kicked off) -> wait until Available. The 4 easy-to-get-wrong params (64-bit / UEFI / QCOW2 / NVMe-after-import) are pinned. Requires aliyun + ossutil authenticated (ALIYUN_REGION + creds). DRAFT — not yet run end-to-end (needs Ali OSS/creds); build-tapp.sh Stage C already dispatches to it for CLOUD=ali. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cvm): NVMe via --Features.NvmeSupport supported --force (CLI schema gap) The aliyun CLI's ModifyImageAttribute schema doesn't expose the Features.NvmeSupport sub-field as a flag (rejects both --Features.NvmeSupport and JSON --Features 'flat format required'); --force skips client validation and sends the flat param the server wants. Validated end-to-end: og-tdx-ali-dev-v0-1-0 imported Available with BootMode=UEFI, Arch=x86_64, NvmeSupport=supported. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ci): wire ali publish into the workflow (EcsRamRole, no secret) The al8 build runner is an Ali ECS instance, so add: - 'Authenticate to Ali' (CLOUD==ali): aliyun configure --mode EcsRamRole (ossutil already uses the instance role) — no AK/SK secret needed. - 'Stage C (ali)' (CLOUD==ali): ./publish-ali-image.sh (OSS + ImportImage), image name carries owner. - ali config as vars: ALIYUN_REGION (ap-southeast-1), ALIYUN_ECS_ROLE (0g-confidential-disk-oss), OSS_BUCKET (0g-confidential-disk). gcp Stage C unchanged (owner-in-name handled by PR #44). Both clouds now publish from one workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(cvm): rewrite README for multi-cloud (gcp+ali) + dimension/platform tables Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(cvm): fix .gitignore paths after gcp-cvm->cvm rename; untrack fde debs The rename left .gitignore pointing at gcp-cvm/*, so the two binary FDE .debs under cvm/ got tracked. Retarget the patterns to cvm/ and drop the debs from git (they stay on disk; placed locally per README prerequisites). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop build-cvm on:release auto-trigger (dispatch-only) (#47) The release auto-trigger only ever built one slice (cloud=gcp + default owner) of the now multi-cloud/multi-owner matrix, and was the sole reason build.yml needed a repo-scoped RELEASE_PAT (GITHUB_TOKEN-created releases don't fire 'on: release'). Remove it: workflow_dispatch is the single entry point, and build.yml's release step goes back to the default GITHUB_TOKEN. The reference-values Open-PR step keeps its RELEASE_PAT || GITHUB_TOKEN fallback (separate use; falls back cleanly if the secret is removed). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * verifier: support UKI reference values in registration + policy (#48) The gcp+uki validation run failed at 'register on AS' with 'measurement.shim empty': register-shared-as.sh hard-required the 5 grub components, and policy.rego only verified the grub chain. UKI images carry a single measurement.uki instead. - policy.rego: add ref_uki + an all_bsa_digests helper + a second boot_chain_ok branch that matches the (specific, known) UKI digest against any boot-services application event. One branch per format; only the format whose ref set is populated fires, so an image verifies as exactly one (fail-closed otherwise). - register-shared-as.sh: inject a literal set per known rule (empty set() when the key is absent) instead of erroring on missing grub keys; require >=1 value overall. Validated with opa eval: grub positive=3 (no regression), uki positive=3, digest-mismatch/cross-format=33 (fail-closed both ways). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: default boot_format to grub for any cloud (uki opt-in) (#49) Drop the per-cloud auto derivation (gcp->grub, ali->uki). boot_format is an independent axis; 'auto' now resolves to grub regardless of cloud, and uki is an explicit opt-in (BOOT_FORMAT=uki / boot_format=uki), for any cloud. Updates build-tapp.sh, prepare-tapp.sh, the build-cvm.yml input description, and README. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop redundant boot_format 'auto' option, default grub directly (#50) 'auto' resolved to grub anyway (per #49), so it was identical to the 'grub' option. Make the input options [grub, uki] with default grub, and simplify the BOOT_FORMAT env to inputs.boot_format directly. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: add opt-in OVERWRITE for publish (delete+recreate existing image) (#51) Version images are immutable by default — a published image may back running confidential instances, and silently replacing it changes measurements and breaks their attestation. But re-running the same version during testing hit a hard 'already exists' failure and required a manual delete. Add OVERWRITE (env / dispatch input 'overwrite', default off): when the target image name already exists, delete-then-recreate instead of failing. Wired into both publish-gcp-image.sh (gcloud images delete) and publish-ali-image.sh (DeleteImage by resolved ImageId), and the build-cvm.yml Stage C steps. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: make boot_format a first-class identifier dimension (name/refval/policy) (#52) grub and uki produce different images with different measurement shapes (grub 5 components / uki 1), but boot_format wasn't in the image name, reference-value path, branch, or AS policy id — so a gcp+uki build clobbered the gcp+grub one (same name/path/PR #42/policy). Add boot_format everywhere cloud is, and drop the redundant 0x owner prefix globally so the longer names still fit GCP's 63-char cap: - image name: <imgbase>-<boot_format>-<version>-<owner40hex> (og-tdx-dev-grub-v0-2-0-<40hex> = 63) - refvalue: verifier/reference-values/<cloud>/<boot_format>/<version>/<env>/<owner>.json - policy id: 0g-tapp-<cloud>-<boot_format>-<version>-<env>-<owner> - branch: refvalues/<cloud>-<boot_format>-<version>-<env>-<owner> - concurrency group also keyed by boot_format owner is normalized identically in all scripts: strip 0x + lowercase. Updates gen-reference-values.sh, open-refvalues-pr.sh, register-shared-as.sh, build-cvm.yml, and both READMEs. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * cvm: add DISK_SIZE + image_name (custom output name) parameters (#54) DISK_SIZE (build-tapp.sh env / build-cvm dispatch input, default 20G): grow the working image up front (qemu-img resize + growpart /dev/sda 1 + resize2fs) before Stage A/convert, so the produced verity-rootfs image can be larger than the cached 20G base. Only grows (>= current is a no-op; shrinking a populated fs is unsafe). image_name (build-cvm dispatch input, default blank): override the published image name; blank keeps the auto <imgbase>-<boot_format>-<version>-<owner>. Applies to both gcp and ali Stage C (use a single env, not both, to avoid a name collision). Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop the advisory boot smoke test step from build-cvm (#55) On this builder (no /dev/kvm) it runs under TCG, so the 600s window is truncated before multi-user (false 'BOOT NOT CONFIRMED' on good images) and it was advisory (continue-on-error) so it never gated — ~10min/build of noise. Keep cvm/test/boot-smoke-test.sh for local / KVM runs; revisit as a hard gate once a KVM-capable runner exists. Real boot + attestation validation is on TDX hardware. Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: move image_name input to the top of the build-cvm dispatch form (#56) Co-authored-by: Wei <wei@0g.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: runtime owner claim — unclaimed boot + measured ClaimOwner RPC - config: owner_address now optional; initial_whitelist removed (use the measured add-to-whitelist RPC after claiming) - server boots UNCLAIMED without an owner; first valid signer of ClaimOwner becomes owner, extended as a claim_owner runtime measurement event - claimed owner persisted under /run (tmpfs) so a process restart within the same boot cannot reopen the claim; config/persisted mismatch refuses to start - legacy baked-in owner still works and is now also measured at startup - cli: claim-owner command with end-to-end verification; examples/claim_owner.sh Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build/ci/docs: drop the per-owner image dimension (ownerless claim-at-runtime images) - build-tapp.sh: OWNER_ADDRESS now optional (legacy); default builds an ownerless image whose owner is claimed at runtime as a measured event - build-cvm.yml: remove the owner input/env and the owner tag from image names/family/concurrency; reference values register under the fixed 'any' owner slot (refvalues scripts unchanged) - docs: README claim-owner section, cvm/README dimensions, reference-values README, EVIDENCE_AND_AS_VERIFICATION claim_owner reconciliation rule - version: tapp-server 0.3.0 (new RPC), tapp-cli 0.3.0 (new command) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: ClaimConfig — extend claim to include chain+kbs runtime config Renames ClaimOwner → ClaimConfig and extends it to carry the full runtime configuration: chain_rpc_url, chain_contract_address, kbs_node_urls. - proto: ClaimConfigRequest has chain/kbs fields; both proto copies synced - server: measurement event is now claim_config and includes all fields; kms_client wrapped in Arc<RwLock> so dynamic-mode kbs_node_urls initialise the KMS client at claim time (no [kbs] section in config.toml needed) - establish_owner_at_startup: passes chain/kbs from config.toml into the claim_config measurement so pre-baked and dynamic modes produce the same event shape — verifiers see the complete runtime config in both modes - cli: claim-config command with --chain-rpc-url / --chain-contract / --kbs-urls - examples/claim_owner.sh: updated for ClaimConfig RPC - e2e: 14/14 with chain+kbs fields in the test claim Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * build: explicit canonical/custom build mode selection Replaces the implicit OWNER_ADDRESS-present-or-not heuristic with an explicit BUILD_MODE parameter (canonical | custom). canonical (default): owner-agnostic image — no owner baked in, one reference set for all owners (refvals under 'any' slot), image name has no owner tag. OWNER_ADDRESS is cleared even if accidentally set. custom: owner baked into config.toml — per-owner initrd measurement, per-owner refval set and image name. Requires OWNER_ADDRESS. build-tapp.sh: BUILD_MODE env var; custom mode enforces OWNER_ADDRESS. build-cvm.yml: build_mode input (choice); owner input (custom only); names step resolves img_suffix + owner_slot per mode; both CI paths (canonical and custom) fully supported. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * build: canonical refvals at <env>.json (no owner subdir/placeholder) Drop the 'any' placeholder — canonical mode uses a clean path without an owner dimension: canonical: verifier/reference-values/<cloud>/<boot_format>/<version>/<env>.json policy id: 0g-tapp-<cloud>-<boot_format>-<version>-<env> custom: verifier/reference-values/<cloud>/<boot_format>/<version>/<env>/<owner>.json policy id: 0g-tapp-<cloud>-<boot_format>-<version>-<env>-<owner> gen-reference-values.sh / register-shared-as.sh / open-refvalues-pr.sh: owner arg is now optional; omit for canonical, pass 0x address for custom. register-shared-as.sh auto-detects owner vs AS-endpoint for arg 5. build-cvm.yml: owner_slot replaced by owner_tag (empty = canonical); scripts called with bare $OWNER_TAG so empty expands to nothing (not an empty string arg). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: require tapp-server >= v0.3.0 for canonical build mode ClaimConfig RPC was introduced in 0.3.0; a canonical image built from an older binary boots unclaimed with no way to claim it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: fail-fast input validation + fix custom-mode owner passing - validate step moved to the very first step (before toolchain/download): canonical requires >= v0.3.0; custom requires an explicit well-formed 0x owner (vars fallback removed — a silently-defaulted owner baked into a measured image is how mistakes ship); image_name+env=both rejected (matrix jobs would clobber each other); release binary existence probed - fix: refvalue steps passed the 0x-stripped owner_tag, but register-shared-as.sh detects owner-vs-endpoint by the 0x prefix — custom mode would have treated the owner as the AS endpoint. Now the raw 0x address is passed everywhere (scripts strip it themselves) and the detection regex also accepts unprefixed 40-hex - OWNER env is empty unless build_mode=custom (canonical steps can never see an owner); BUILD_MODE forwarded to build-tapp.sh explicitly - docs: drop remaining 'any'-slot references; canonical/custom identifier tables; claim_config event shape in EVIDENCE_AND_AS_VERIFICATION.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build: KBS_URLS optional in canonical mode canonical images can receive kbs_node_urls at runtime via ClaimConfig; custom mode still requires KBS_URLS to be baked in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: fix KBS_URLS passing (inline quoted assignment) + normalize literal [] - $KBS_ARG expanded from a variable is not parsed as an env assignment by the shell — with vars.TAPP_KBS_URLS='[]' the word KBS_URLS=[] became the command name ('command not found'). Keep the assignment inline and quoted. - build-tapp.sh: treat a literal [] as empty (would interpolate into node_urls = [[]], broken TOML). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: fix env passing to build-tapp.sh — no $VAR in assignment-prefix list bash decides what is an assignment prefix BEFORE expansion: a variable ($EXTRA/$KBS_ARG, even when empty) in the prefix list turns every later VAR=val word into the command name — the actual cause of both 'KBS_URLS=[]: command not found' failures. BUILD_MODE/KBS_URLS are already inherited from the job env; custom mode exports OWNER_ADDRESS explicitly. Verified locally with a simulated step for both modes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * skill: update 0g-tapp-cli v1.4.0 — claim-config, unclaimed state, new policy id format Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * cvm: RuntimeDirectoryPreserve=yes — keep claimed_owner across process restarts systemd removes RuntimeDirectory on stop by default; restart = stop+start, so /run/tapp/claimed_owner was wiped on every systemctl restart, allowing re-claim. Preserve=yes keeps the dir across restarts but still clears it on VM reboot (tmpfs) — matching the RTMR lifetime. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: get-tapp-info shows runtime chain + kbs from ClaimConfig - proto: add ChainConfigInfo message and chain field to TappConfigInfo - server: ClaimedRuntimeConfig stores chain/kbs set at claim time; get_tapp_info reads runtime values, falls back to static config - cli: display Chain and KBS sections in get-tapp-info output - e2e: 14/14 still passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Wei <wei@0g.ai> * chore(verifier): reference values for gcp/grub/v0.2.0/dev/ea695c312ce119de347425b29aff85371c9d1837 (#57) Co-authored-by: cvm-ci <cvm-ci@0g.ai> * feat(verify-app): claim_config owner reconcile + no-policy boot-chain output Squash of the feat/runtime-owner-claim commits that missed the #58 merge: - verify-app chain mode reconciles the claim_config event owner vs the on-chain app owner → owner✓/✗/? in the reconcile line - without --policy-ids, both modes print boot-chain component digests in reference-value JSON (same selection rules as policy.rego: shim/grub/uki from EV_EFI_BOOT_SERVICES_APPLICATION device paths, kernel/initrd/cmdline from EV_IPL strings) — directly diffable against the refvalue json - fix: claim-config always overwrites kms_client when kbs_node_urls given (empty [kbs] node_urls=[] stub no longer blocks dynamic KMS init) - get-tapp-info shows runtime chain+kbs set via ClaimConfig - docs/skill: two-axis verify model (chain=dynamic refs, policy=static refs) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(verifier): reference values gcp uki v0.3.0 dev (#65) Co-authored-by: cvm-ci <cvm-ci@0g.ai> * fix(cvm): ali image poll — DescribeImages defaults to Available-only A just-imported image (Creating/Waiting) is invisible to the default DescribeImages filter, so the first poll read the empty result as 'Gone' and failed the publish instantly. Query all lifecycle states explicitly; treat not-yet-listed as Pending (keep polling), fail only on CreateFailed/UnAvailable. Same fix for the OVERWRITE pre-check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cvm): ali images need HWE kernel — TDX RTMR extend requires >=6.16 The noble GA generic kernel (6.8) lacks the TDX runtime-measurement interface (sysfs measurements/rtmrN:sha384 + EXTEND_RTMR ioctl, mainlined in 6.16), so every extend_runtime_measurement on the Ali image failed (claim_config/start_app not measured). Install linux-image-generic-hwe-24.04 (currently 6.17, same generation as the gcp kernel), point the /boot symlinks at it, and purge the 6.8 kernel (no alternate boot path, smaller image). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cvm): ali kernel — pick newest generic WITH modules-extra, not the HWE meta The HWE meta raced ahead of the archive: it pulled linux-image-7.0.0-28-generic whose linux-modules-extra (needed by cryptpilot-convert for zram) is not published, failing the build. Select the newest versioned generic kernel that has its modules-extra available (currently the 6.17 line), install both explicitly, and require >=6.16 (TDX RTMR extend). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(verifier): reference values ali uki v0.3.0 dev (#68) Co-authored-by: cvm-ci <cvm-ci@0g.ai> * fix(server): measure_only resolved ${VAR} image refs literally — empty hashes on-chain compose_service_images parsed the RAW compose YAML, so interpolated refs (image: ${REGISTRY_PREFIX:-…}/foo) stayed literal, docker inspect failed, and the empty digest was SILENTLY registered on-chain by start-app --register-onchain (update-onchain later 'fixed' it because the real start path reads digests from running containers, where compose did the interpolation). - resolve service→image via `docker compose config --format json` in the staged dir (full interpolation incl. the staged .env) - fail the measure task loudly if any pulled service still has no digest (never register an empty image hash again) Verified live on 127.0.0.1:50099: image ${TEST_IMG:-nginx:alpine} → sha256:4a73073b…; volumes hash unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(tapp-server): bump version to 0.3.1 PATCH release: measure_only ${VAR} image-ref interpolation fix (empty imageHashes registered on-chain by start-app --register-onchain). tapp-cli unchanged (stays 0.3.0). Skill troubleshooting updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Revert "chore(tapp-server): bump version to 0.3.1" This reverts commit e85419fabf977d65c0ebf5d936d2471033bd5e2c. * chore(tapp-server): bump version to 0.3.1 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: keep tapp-server at 0.3.0 (fold measure fix into the 0.3.0 release) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Wei <wei@0g.ai> Co-authored-by: cvm-ci <cvm-ci@0g.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft / WIP — the pieces + design are here and syntax/YAML-checked; the pipeline has not been run end-to-end as a whole yet (no self-hosted runner wired, no secrets). Opening as a draft for review + tracking. Do not merge.
What this automates
Turns "tapp releases a version" into a reproducible, attestable og-tdx CVM image, on the same Alinux/al8 host we build on today. Follow-up to #5 / #29.
Dimensions:
version × env × ownerv0.1.0) — tapp-server + image name suffix.dev(HARDEN=0, SSH-able) /prod(HARDEN=1, hardened) — the two image variants.OWNER_ADDRESS, baked into/etc/tapp/config.toml.policy.regofolds rootfs integrity into the initrd measurement, so a different owner ⇒ a differentmeasurement.initrd⇒ a distinct reference set → owner is an attested dimension. KBS/KMS is fixed (vars.TAPP_KBS_URLS).Flow (
.github/workflows/build-cvm.yml,workflow_dispatch)inputs(version, env, owner, publish)→ on[self-hosted, alinux, cvm-builder]:build-gcp-tapp.sh, HARDEN per env, OWNER baked, KBS fixed)publish-gcp-image.sh) →og-tdx[-dev]-<version>(+ family)ci/build-cryptpilot-fde.sh) — transparent, not a prebuilt blobshow-reference-value→verifier/reference-values/<version>/<env>/<owner>.json(ci/gen-reference-values.sh) → register on the shared AS (register-shared-as.sh, now owner-dimensioned)devFiles
.github/workflows/build-cvm.ymlverifier/register-shared-as.sh— add owner dimension (path + policy id0g-tapp-<version>-<env>-<owner>)gcp-cvm/ci/{build-cryptpilot-fde,gen-reference-values,fetch-inputs,open-refvalues-pr}.shverifier/reference-values/README.md— owner-dimensioned layout + fork-build flowTested / not
gen-reference-values.shvalidated end-to-end against a real image (fixed fork binary emits themeasurement.*JSON directly).build-gcp-tapp.sh(A/B) andpublish-gcp-image.sh(C) already proven on real GCP images this cycle.fetch-inputs,open-refvalues-pr,register-shared-as(owner), the smoke-test gate, and the workflow itself.Open items before "Ready for review"
GCP_SA_KEY+ varTAPP_KBS_URLS.publish=false.register-shared-as.shusesgrpcurl -plaintextwith no token — confirm how the shared ASSetAttestationPolicyis authorized (network ACL vs token) and align.verifier/reference-values/v0.1.0/{dev,prod}.jsonto the owner-dimensioned layout.CRYPTPILOT_REFto a full SHA/tag (currently short52a77a2).🤖 Generated with Claude Code