Skip to content

ci: prepare reviewed node digest promotion - #310

Closed
samgbafa wants to merge 4 commits into
mainfrom
skgbafa/tc-959-node-logship-workflow
Closed

samgbafa wants to merge 4 commits into
mainfrom
skgbafa/tc-959-node-logship-workflow

Conversation

@samgbafa

@samgbafa samgbafa commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Scope and review status

TC-959 workflow/config/focused-test changes. No merge or deployment authorization. Amos owns routing the completed delta to the same existing #310 Astra reviewer.

  • API head: 87b2a38b2ad523bf6587e3c08ef1e072138b56e7
  • Final API base: 7a704a10d577d5a4f9b882fa16f17666a3ebc72b
  • Original worktree baseline: exact main d7b707e7a9d6cf68b67630da43cbeed2a41de907; no rebase.

Files: .github/workflows/docker.yml, ops/quota-overrides.json, scripts/ci/test-quota-overrides.py, scripts/ci/fixtures/tc959-n0-compose.yaml, and scripts/check-deployment-policy-probes.mjs. The fixture is an offline copy of the approved artifact, not a production compose change. No application, Rust, production compose, ingress, or production script source changes.

Reviewed configuration identity

Companion #309 is approved at exact head 44eeb20a1461ef66b5af492e3be4c42be2728b67, base 13cbcdcd20b4fb546754aa60d7043122ca56ed1f: Astra review comment. Its hosted acceptance run passed the 500/500 distinct fast-503 burst, continuity/privacy/isolation and cleanup gates.

The independently derived raw-file SHA256 of docker-compose.dstack-postgres.yaml at that reviewed head is:

ca110b0bae6d5a06a1002fb80df3f71cffec11659c9f9dbfa5d72624735546f6

For the exact N0 image, pinning verifies these raw bytes before any compose rewrite, sealed deploy or CVM mutation. No caller-supplied hash is accepted. The pin step emits n0_configuration_verified=true only after successful validation; quota restoration also requires that proof. Any config/profile/line-ending change requires renewed review and a deliberate pin update. Normal non-N0 :dstack image replacement remains supported; it does not authorize this quota snapshot. The N0 digest path requires the exact approved already-pinned compose, not a tag-form substitute.

Immutable promotion identity

Field Required value
Compose path docker-compose.dstack-postgres.yaml
Composed node image ghcr.io/tinycloudlabs/tinycloud-node@sha256:4bdae4fc232d886bb56bfbce4ec62ed4b8fb4e1369a01f6afb73d06dd68c188f
OCI version label 1.20.6-dstack
OCI revision label e18578ff9162986ddd813259c60f73866e3e60d1
Served/dispatch image version 1.20.6
Unchanged ingress image ghcr.io/tinycloudlabs/tinycloud-node-ingress@sha256:63c1c44e2ded5b73a5f174807b91ca677826974df0f9ab3045f6ec87865a5744
Better Stack source 2795437 only

The existing digest is pulled and label-checked, not rebuilt, retagged or relabelled. Validated image labels drive outputs, summaries, served-version checks and watch messages; the checkout crate version is not recorded as the promoted image version. Ordinary builds still enforce their checkout version. Duplicate/missing native references, unrelated pins and wrong revision/version labels reject.

Event guards: merging deploys nothing

deploy-phala.if requires github.event_name == 'workflow_dispatch', inputs.deploy_phala, successful version/cutover/history guards, no rollback audit and no cancellation. The digest path additionally requires refs/heads/main and skipped node build jobs; the normal path retains its successful-build requirements. Recorded rollback revisions must remain in main history; existing ancestry and explicit non-descendant controls remain.

A merge emits push, not workflow_dispatch: deploy is skipped, its quota steps cannot execute, and promote-watch skips a skipped deploy. Separately configured CI image builds/publishes can still run. #306's release-only node latest/dstack floating-tag rules are unchanged; a manual promotion does not move them.

Sealed --validate-config runs for normal deployments and for this exact old digest even with recorded rollback labels. Unrelated older emergency images retain their compatibility skip; normal/history/ancestry guards are not bypassed.

Sealed token and complete allowed_envs

Name-only mapping:

secrets.PROD_TINYCLOUD_BETTERSTACK_SOURCE_TOKEN → PHALA_ENV_BETTERSTACK_SOURCE_TOKEN → sealed .phala-prod.env key BETTERSTACK_SOURCE_TOKEN.

Required inputs fail before deploy. umask 077 and chmod 0600 restrict the env file before values are written. CR/LF, duplicate/invalid names, empty values and CLI/SDK dotenv interpretation drift fail closed. Compose validation remains quiet; sealed CLI output is suppressed. A trap and always-cleanup remove the file; no dump or artifact upload.

Existing phala@1.1.19 uses isolated @phala/cloud@0.2.9, API version 2026-01-21, finite timeout and no transport retries. Before deploy and again before PATCH, every live allowed name must have a known value in the complete env file; retaining an old name cannot recover omitted ciphertext. Unknown old names block before mutation. A complete whitelist is an idempotent no-op that still verifies token presence by NAME.

Otherwise the complete set is encrypted and PATCH contains only encrypted_env + env_keys, never an allowed_envs input. On-chain precondition_required fails closed; no blind compose-hash retry or invented transaction proof. Contracts: updateCvmEnvs and environment-update API.

After PATCH, completion requires an observed matching operation/progress or non-running lifecycle transition, then settled running status and fresh complete whitelist readback. Optional absent/null progress is supported only after observing the operation. A fast/unobserved queued update deliberately fails the deadline. SDK DEBUG is cleared; only fixed phase/status/count diagnostics appear, never raw responses/messages, exception details, values or ciphertext.

Operator sequencing risk: public docs do not establish production restart timing. Synthetic completion tests do not prove production timing; verify the last update/restart has completed before relying on health/quota restoration. Existing --no-public-logs --no-public-sysinfo remain; no TCB-visibility API/flag is added. Operator pre/post readback must preserve logs/sysinfo/tcbinfo = false/false/true.

Reviewed quota restoration after the last restart

restore_n0_quota_snapshot defaults false. Selecting the exact N0 digest without explicit opt-in rejects before mutation; opt-in also requires main-only dispatch and exact image/version/revision. Restoration additionally requires the approved compose proof. This is a reviewed snapshot, not evergreen quota policy; refresh values and authorization when TC-1161 changes them or before unrelated future restarts.

In-memory overrides vanish on restart. Restoration runs inside the deploy job, after env synchronization and required version/ingress health, before Policy probes. Policy or downstream watch failure cannot skip completed restoration; no second environment approval/queue and no deploy-job actions: write expansion.

The repository-scoped secret NAME PROD_TINYCLOUD_ADMIN_SECRET is used only inside Actions, as an in-memory TLS header, not argv. Before ANY PUT, authenticated GET /admin/usage must prove both canonical full IDs exactly once with positive known integer usage. Missing/duplicate/null/zero/unknown targets fail without PUTs. The reviewed config/runtime/tests pin the canonical Sam owner and both limits; a different owner's positive-usage spaces fail before any API request. Quota GET alone is not existence evidence.

Each PUT is followed by GET verifying exact identity, override_bytes and effective_limit_bytes. Only transport failures receive bounded retries (three attempts); HTTP failures, including 5xx, fail. Diagnostics never dump inventory/raw responses/IDs.

Limits: applications 5368709120; secrets 268721121. Applications provenance is TC-1161 D1. Secrets ID is verified by Amos via Sam's release-keys/monitoring profile metadata; its D1 parity task remains pending. No credential/profile values were requested or read.

Any attempted CVM mutation without verified quotas—including sync/health failure or a skipped restore—emits fatal OVERRIDES UNVERIFIED in the job and summary. Authorized TC-1161/internal storage emergency-button intervention remains the fallback.

Profile/privacy compatibility

The bound #309 bytes default to errors_slow: valid status >=400 OR node latency_us > 1000000 / ingress request_time_seconds > 1.0. Equality alone does not qualify. all_requests is explicit; valid typed WARN/ERROR diagnostics remain eligible under either profile as fixed-code occurrence counts. Fresh allowlisted objects exclude raw messages/errors, targets, spans, SQL, URI/query values, headers, bodies, credentials and identities; malformed/plaintext/oversized records retain the documented rejection/transport limitations.

The node/ingress json-file→GELF cutover disables raw Docker caching; loopback UDP is nonblocking and best-effort. Vector uses the reviewed bytes-decoder/privacy boundary, not an unsafe raw fallback. Production delivery/volume/privacy checks still belong to the authorized operator.

Conditional operator command — NOT EXECUTED

Only after #310 delta approval, both reviewed changes are merged without artifact drift, current-main/history/cutover checks pass, quotas are freshly authorized and a named operator is approved:

gh workflow run docker.yml -R TinyCloudLabs/tinycloud-node --ref main \
  -f rollback_audit_only=false \
  -f include_duckdb=false \
  -f include_tc_bench_v1=false \
  -f deploy_phala=true \
  -f restore_n0_quota_snapshot=true \
  -f allow_non_descendant=false \
  -f sql_identity_fence=false \
  -f image_version=1.20.6 \
  -f deploy_image_digest=sha256:4bdae4fc232d886bb56bfbce4ec62ed4b8fb4e1369a01f6afb73d06dd68c188f \
  -f rollback_image_version=1.20.6 \
  -f rollback_image_revision=e18578ff9162986ddd813259c60f73866e3e60d1

All 11 inputs remain unchanged from the reviewed packet. No dispatch was executed.

Rollback means redeploy Amos's captured pre-N0 compose, not merely select the unchanged image: /home/tinycloud/scratch/tc959-n0-release/rollback-compose.yaml, recorded raw SHA256 df239749ca0a9c09893a855ae2a8abb94090b8556515ad002105c58ac91f4c91; Amos must revalidate it. Its json-file/raw-retention consequence requires an explicit privacy decision. restore_n0_quota_snapshot=true authorizes only the bound N0 artifact; it is NOT a bypass for the pre-N0 compose. False/omitted opt-in also cannot bypass the N0 digest guard. A separately reviewed/authorized rollback path and quota restoration/fallback decision are required; do not reuse this promotion command as a rollback command. TC-1302 rollback remains gated by that operator packet.

Exercised verification

  • Tests-first binding: eight new failing subcases before the fix; 18/18 focused methods pass afterward. The approved fixture is accepted unchanged; alpha/beta, profile drift, CRLF drift and tag-form N0 substitutions reject before intercepted mutation. Normal non-N0 :dstack still passes. Quota gating rejects absent/false compose proof.
  • Three strict-shell runtime regressions: red before baseline fix, green afterward; quota/report/watch explicitly use set -euo pipefail.
  • Policy/ingress checker: 13 synthetic scenarios pass. Docker-tag matrix: 8/8. Deploy-ref guard matrix: 16/16.
  • Throwaway packet check: 11/11 dispatch input names/types match the workflow; manual-main digest selection passes, push/PR/release/non-main/cancelled/cutover/failed-guard cases reject, normal successful-build dispatch remains valid. Selected normal/N0 preflight and skipped unrelated-old-image cases pass. No workflow was dispatched.
  • actionlint v1.7.12 -shellcheck= -pyflakes= .github/workflows/docker.yml: clean; external shellcheck/pyflakes plugins not exercised.
  • Ten-step throwaway smoke executes actual extracted selection/pin/preflight/sealing/sync/health/quota/Policy/watch scripts against offline CLI/SDK/HTTP boundaries. Observed order: deploy → PATCH → pending/running → version/ingress health → inventory → PUT/GET twice → Policy → intercepted watch. Both quota readbacks verify; compose hash remains exact; only truthful 1.20.6/e18578 identity is emitted; no canary leakage or sealed-file residue. No real network/dispatch.
  • Six additional offline sealing/sync scenarios cover complete-whitelist no-op, delayed update, missing token, unknown old value, malformed env name and suppressed CLI error. Current Add a compose-only node log sidecar #309 profile/privacy contract guards: 17/17, plus its exact-head native boundary/hosted acceptance above.
  • Shared local slot checked/claimed/released. No local Docker, production/Phala requests, credentials read, merge, dispatch or deploy. No local Rust build/test suite; hosted full CI runs below cover configured workspace checks.

Exact-head CI and remaining gates

All runs below belong to 87b2a38b2ad523bf6587e3c08ef1e072138b56e7 and are successful. Final API rollup: 19 terminal checks — 10 success, 9 intentional skips; zero failing or pending. API head/base unchanged.

  • Workspace CI: success — fmt, clippy, workspace/WASM builds, PostgreSQL-backed and DuckDB tests, M1, capability/probe/deploy-ref checks.
  • Docker Container: success — version guard/18 quota-boundary methods, ingress logging/build and primary image build; deploy-phala/promote-watch skipped.
  • Release PR checks: success; publish jobs skipped.

Amos owns routing this completed delta to the same existing #310 Astra reviewer. No new reviewer, merge or deployment is requested here. Operator gates remain: refreshed Sam quota authorization, complete sealed-name inventory, last-update/restart settlement, privacy readback, signed read/write/dense-watch proof, measured volume allowance and counts-only/forbidden-field verification in source 2795437.

@samgbafa

Copy link
Copy Markdown
Contributor Author

REQUEST_CHANGES: see TC-959

@samgbafa

Copy link
Copy Markdown
Contributor Author

APPROVE: see TC-959

@samgbafa

Copy link
Copy Markdown
Contributor Author

Landed via #313 (07d23ae), which contains this branch's head 87b2a38 unchanged. See TC-959.

@samgbafa samgbafa closed this Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant