diff --git a/Dockerfile b/Dockerfile index 249f43a..b788b66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,17 +4,29 @@ FROM python:3.12-slim AS paper-engine ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - ZERO_MODE=paper + ZERO_MODE=paper \ + ZERO_FOUNDATION_STATE_DIR=/data/.zero \ + ZERO_FOUNDATION_HEARTBEAT_ENABLED=true WORKDIR /app -COPY engine/pyproject.toml engine/README.md /app/engine/ -COPY engine/src /app/engine/src +RUN useradd --create-home --shell /usr/sbin/nologin zero \ + && mkdir -p /data/.zero \ + && chown -R zero:zero /data /app + COPY examples /app/examples COPY scripts/railway_start.sh /app/scripts/railway_start.sh RUN python -m pip install --no-cache-dir --upgrade pip \ - && python -m pip install --no-cache-dir /app/engine \ - && chmod +x /app/scripts/railway_start.sh + && python -m pip install --no-cache-dir zero-engine==0.1.6 \ + && chmod +x /app/scripts/railway_start.sh \ + && chown -R zero:zero /app + +USER zero + +EXPOSE 8765 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD python -c "import json, os, urllib.request; port=os.environ.get('PORT', '8765'); payload=json.load(urllib.request.urlopen(f'http://127.0.0.1:{port}/health', timeout=3)); raise SystemExit(0 if payload.get('status') == 'ok' else 1)" CMD ["zero-paper-demo"] diff --git a/contracts/distribution/railway-template.json b/contracts/distribution/railway-template.json index 874da5d..5278437 100644 --- a/contracts/distribution/railway-template.json +++ b/contracts/distribution/railway-template.json @@ -1,7 +1,7 @@ { "checks": { "deploy_docs_name_doctor": true, - "dockerfile_installs_engine": true, + "dockerfile_installs_claim_capable_package": true, "dockerfile_present": true, "partner_docs_name_application_url": true, "partner_docs_name_support_commitment": true, @@ -9,7 +9,9 @@ "partner_docs_name_template_url": true, "partner_docs_present": true, "railway_healthcheck_configured": true, + "railway_start_names_runtime_version": true, "railway_start_uses_durable_journal": true, + "railway_start_uses_foundation_state_dir": true, "railway_start_uses_port": true, "railway_toml_present": true, "railway_uses_dockerfile": true, @@ -20,15 +22,18 @@ "schema_version": "zero.railway_template_packet.v1", "start_script_present": true, "template_docs_include_marketplace_overview": true, + "template_docs_name_bootstrap_readback": true, + "template_docs_name_claim_success_log": true, "template_docs_name_dashboard_generation": true, "template_docs_name_icon": true, "template_docs_name_marketplace_icon": true, "template_docs_name_partner_publish_step": true, "template_docs_name_public_demo": true, + "template_docs_name_runtime_package": true, "template_docs_name_template_url": true, "template_icon_present": true }, - "generated_at": "2026-05-05T07:51:46Z", + "generated_at": "2026-07-01T08:05:00Z", "marketplace": { "create_flow": "Project Settings -> Generate Template from Project", "deploy_button_markdown": "[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/zero-paper-runtime)", @@ -66,33 +71,36 @@ "builder": "DOCKERFILE", "dockerfile_path": "Dockerfile", "healthcheck_path": "/health", + "runtime_package": "zero-engine==0.1.6", "start_command": "/app/scripts/railway_start.sh" }, "doctor_summary": { - "fail": 0, - "ok": 17, - "warn": 1 + "reason": "0.1.6 claim-capable template source changed after the last live demo evidence bundle", + "status": "pending_after_marketplace_republish" }, "environment": "production", - "evidence_bundle_at_generation": "artifacts/deployment-evidence/20260504T192122Z", + "evidence_bundle_at_generation": null, "evidence_verify": { - "checks": 60, - "fail": 0, - "ok": true + "ok": null, + "status": "pending_after_marketplace_republish" }, "partner_application": "template_published_partner_pending", "partner_application_packet": "docs/railway-partner.md", "project": "fabulous-enchantment", "public_demo_url": "https://zero-production-5214.up.railway.app", "service": "zero", - "status": "published_on_marketplace", + "status": "pending_marketplace_republish_after_runtime_package_update", "template_name": "ZERO Paper Runtime", "template_url": "https://railway.com/deploy/zero-paper-runtime", - "verified_deployment_at_generation": "ea8424de-f5e3-4227-84a3-67970f5b0cfd", + "verified_deployment_at_generation": null, "workspace": "zero" }, "template_variables": { + "ZERO_CLAIM_TOKEN": "", + "ZERO_CONTROL_URL": "", "ZERO_DEPLOYMENT_ID": "zero-railway-public-paper", + "ZERO_DEPLOYMENT_VERSION": "engine-v0.1.6", + "ZERO_FOUNDATION_STATE_DIR": "/data/.zero", "ZERO_HYPERLIQUID_LIVE_PRICES": "true", "ZERO_INTELLIGENCE_API_ACCOUNT_ID": "acct_railway", "ZERO_INTELLIGENCE_API_PLAN": "free", diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 9e6b059..0fbe578 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -2297,6 +2297,10 @@ The latest Railway Template Publish Packet is committed at It records the live demo URL, point-in-time verified deployment id, evidence bundle path, doctor result, required variables, autodeploy config, and marketplace publish state. +After template-impacting runtime package changes, the packet must mark live +evidence as pending until the Railway project is rebuilt from `main`, the +marketplace template is regenerated, and a fresh doctor/evidence bundle is +captured. ## Common Use Cases @@ -2309,7 +2313,10 @@ marketplace publish state. ## Dependencies for ZERO Hosting - Railway service sourced from `https://github.com/zero-intel/zero`. -- Dockerfile build from the repository root. +- Dockerfile build from the repository root. The root Dockerfile installs the + claim-capable public PyPI package `zero-engine==0.1.6` so the marketplace + template can consume Foundation claim bootstrap without copying commercial app + code into the public repo. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`; do not hardcode a port. @@ -2335,6 +2342,7 @@ marketplace publish state. | Branch | `main` | | Builder | Dockerfile | | Dockerfile path | `Dockerfile` | +| Runtime package | `zero-engine==0.1.6` | | Start command | from `railway.toml`: `/app/scripts/railway_start.sh` | | Healthcheck path | `/health` | | Healthcheck timeout | `60` | @@ -2377,7 +2385,7 @@ before any live authority ceremony. ## Dependencies for ZERO Hosting - Railway service sourced from `https://github.com/zero-intel/zero`. -- Dockerfile build from the repository root. +- Dockerfile build from the repository root, installing `zero-engine==0.1.6`. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`. @@ -2397,12 +2405,28 @@ inspectable, interruptible, and paper-first by default. | `ZERO_MODE` | yes | `paper` | Keeps the template in paper mode. | | `ZERO_JOURNAL_PATH` | yes | `/data/decisions.jsonl` | Durable append-only paper journal on the Railway volume. | | `ZERO_HYPERLIQUID_LIVE_PRICES` | yes | `true` | Uses Hyperliquid public mids as read-only market data. | +| `ZERO_DEPLOYMENT_VERSION` | yes | `engine-v0.1.6` | Identifies the claim-capable runtime package in `/deployment/claim` readbacks. | +| `ZERO_FOUNDATION_STATE_DIR` | yes | `/data/.zero` | Persists the scoped Foundation API key and runtime keys on the Railway volume after a successful claim. | +| `ZERO_CONTROL_URL` | app-connected only | empty | ZERO app/control-plane URL from Studio Vibe Deploy, usually `https://getzero.dev`. | +| `ZERO_DEPLOYMENT_ID` | app-connected only | `zero-railway-public-paper` | Replace with the deployment id minted by Studio before app binding. | +| `ZERO_CLAIM_TOKEN` | app-connected only | empty | One-time claim ticket copied from Studio. Keep hidden in Railway; do not publish it in logs or docs. | | `ZERO_INTELLIGENCE_STORE_PATH` | yes | `/data/zero/intelligence.jsonl` | Durable aggregate store for hosted-compatible Intelligence reference endpoints. | | `ZERO_INTELLIGENCE_API_TOKEN` | optional | empty | Disposable demo bearer token for hosted-compatible protected Intelligence routes. | | `ZERO_INTELLIGENCE_API_PLAN` | optional | `free` | Reference account plan shown by local hosted-compatible packets. | | `ZERO_INTELLIGENCE_API_ACCOUNT_ID` | optional | `acct_railway` | Public-safe local account label for reference packets. | | `ZERO_INTELLIGENCE_WEBHOOK_SIGNING_KEY` | optional | `${{secret(64, "abcdef0123456789")}}` | Demo HMAC key for webhook fixture signatures. Never reuse production keys. | +Successful app-connected boots must print: + +```text +zero foundation deployment claimed: claim_token_consumed=true runtime_base_url=https:// +``` + +`/deployment/claim` and `/deployment/heartbeat` must also expose public-safe +`runtime.foundation_bootstrap_*` fields. If the runtime reports version `0.1.5` +or omits those fields, rebuild the Railway service from the current template +before recovering another claim ticket. + ## Railway Template Publish Packet The packet schema is `zero.railway_template_packet.v1`. Regenerate it after any diff --git a/docs/railway-template.md b/docs/railway-template.md index 6e28fb6..794d383 100644 --- a/docs/railway-template.md +++ b/docs/railway-template.md @@ -30,6 +30,10 @@ The latest Railway Template Publish Packet is committed at It records the live demo URL, point-in-time verified deployment id, evidence bundle path, doctor result, required variables, autodeploy config, and marketplace publish state. +After template-impacting runtime package changes, the packet must mark live +evidence as pending until the Railway project is rebuilt from `main`, the +marketplace template is regenerated, and a fresh doctor/evidence bundle is +captured. ## Common Use Cases @@ -42,7 +46,10 @@ marketplace publish state. ## Dependencies for ZERO Hosting - Railway service sourced from `https://github.com/zero-intel/zero`. -- Dockerfile build from the repository root. +- Dockerfile build from the repository root. The root Dockerfile installs the + claim-capable public PyPI package `zero-engine==0.1.6` so the marketplace + template can consume Foundation claim bootstrap without copying commercial app + code into the public repo. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`; do not hardcode a port. @@ -68,6 +75,7 @@ marketplace publish state. | Branch | `main` | | Builder | Dockerfile | | Dockerfile path | `Dockerfile` | +| Runtime package | `zero-engine==0.1.6` | | Start command | from `railway.toml`: `/app/scripts/railway_start.sh` | | Healthcheck path | `/health` | | Healthcheck timeout | `60` | @@ -110,7 +118,7 @@ before any live authority ceremony. ## Dependencies for ZERO Hosting - Railway service sourced from `https://github.com/zero-intel/zero`. -- Dockerfile build from the repository root. +- Dockerfile build from the repository root, installing `zero-engine==0.1.6`. - Public HTTP networking enabled for the runtime service. - Persistent Railway volume mounted at `/data`. - Railway-provided `PORT`. @@ -130,12 +138,28 @@ inspectable, interruptible, and paper-first by default. | `ZERO_MODE` | yes | `paper` | Keeps the template in paper mode. | | `ZERO_JOURNAL_PATH` | yes | `/data/decisions.jsonl` | Durable append-only paper journal on the Railway volume. | | `ZERO_HYPERLIQUID_LIVE_PRICES` | yes | `true` | Uses Hyperliquid public mids as read-only market data. | +| `ZERO_DEPLOYMENT_VERSION` | yes | `engine-v0.1.6` | Identifies the claim-capable runtime package in `/deployment/claim` readbacks. | +| `ZERO_FOUNDATION_STATE_DIR` | yes | `/data/.zero` | Persists the scoped Foundation API key and runtime keys on the Railway volume after a successful claim. | +| `ZERO_CONTROL_URL` | app-connected only | empty | ZERO app/control-plane URL from Studio Vibe Deploy, usually `https://getzero.dev`. | +| `ZERO_DEPLOYMENT_ID` | app-connected only | `zero-railway-public-paper` | Replace with the deployment id minted by Studio before app binding. | +| `ZERO_CLAIM_TOKEN` | app-connected only | empty | One-time claim ticket copied from Studio. Keep hidden in Railway; do not publish it in logs or docs. | | `ZERO_INTELLIGENCE_STORE_PATH` | yes | `/data/zero/intelligence.jsonl` | Durable aggregate store for hosted-compatible Intelligence reference endpoints. | | `ZERO_INTELLIGENCE_API_TOKEN` | optional | empty | Disposable demo bearer token for hosted-compatible protected Intelligence routes. | | `ZERO_INTELLIGENCE_API_PLAN` | optional | `free` | Reference account plan shown by local hosted-compatible packets. | | `ZERO_INTELLIGENCE_API_ACCOUNT_ID` | optional | `acct_railway` | Public-safe local account label for reference packets. | | `ZERO_INTELLIGENCE_WEBHOOK_SIGNING_KEY` | optional | `${{secret(64, "abcdef0123456789")}}` | Demo HMAC key for webhook fixture signatures. Never reuse production keys. | +Successful app-connected boots must print: + +```text +zero foundation deployment claimed: claim_token_consumed=true runtime_base_url=https:// +``` + +`/deployment/claim` and `/deployment/heartbeat` must also expose public-safe +`runtime.foundation_bootstrap_*` fields. If the runtime reports version `0.1.5` +or omits those fields, rebuild the Railway service from the current template +before recovering another claim ticket. + ## Railway Template Publish Packet The packet schema is `zero.railway_template_packet.v1`. Regenerate it after any diff --git a/scripts/hardening_gate.sh b/scripts/hardening_gate.sh index 1c27bce..2ab88c2 100755 --- a/scripts/hardening_gate.sh +++ b/scripts/hardening_gate.sh @@ -155,7 +155,9 @@ contains "https://station.railway.com/my-template-queue" docs/railway-partner.md contains "scripts/homebrew_formula.py" docs/distribution.md contains "zero.registry_launch_packet.v1" docs/registry-launch.md contains "zero.railway_template_packet.v1" contracts/distribution/railway-template.json -contains "published_on_marketplace" contracts/distribution/railway-template.json +contains "pending_marketplace_republish_after_runtime_package_update" contracts/distribution/railway-template.json +contains "zero-engine==0.1.6" contracts/distribution/railway-template.json +contains "pending_after_marketplace_republish" contracts/distribution/railway-template.json contains "template_published_partner_pending" contracts/distribution/railway-template.json contains "docs/assets/zero-template-icon.svg" contracts/distribution/railway-template.json contains "https://railway.com/deploy/zero-paper-runtime" contracts/distribution/railway-template.json diff --git a/scripts/public_readiness_gate.sh b/scripts/public_readiness_gate.sh index be0c68b..068ccdf 100755 --- a/scripts/public_readiness_gate.sh +++ b/scripts/public_readiness_gate.sh @@ -313,7 +313,9 @@ file_contains "brew tap zero-intel/zero" README.md file_contains "https://zero-production-5214.up.railway.app" README.md file_contains "https://railway.com/deploy/zero-paper-runtime" README.md file_contains "zero.railway_template_packet.v1" contracts/distribution/railway-template.json -file_contains "published_on_marketplace" contracts/distribution/railway-template.json +file_contains "pending_marketplace_republish_after_runtime_package_update" contracts/distribution/railway-template.json +file_contains "zero-engine==0.1.6" contracts/distribution/railway-template.json +file_contains "pending_after_marketplace_republish" contracts/distribution/railway-template.json file_contains "template_published_partner_pending" contracts/distribution/railway-template.json file_contains "ZERO Paper Runtime" contracts/distribution/railway-template.json file_contains "https://railway.com/deploy/zero-paper-runtime" contracts/distribution/railway-template.json diff --git a/scripts/railway_smoke.sh b/scripts/railway_smoke.sh index 7ac8670..6aa5660 100755 --- a/scripts/railway_smoke.sh +++ b/scripts/railway_smoke.sh @@ -6,6 +6,7 @@ HOST_PORT="${ZERO_RAILWAY_SMOKE_PORT:-18765}" CONTAINER_NAME="zero-railway-smoke-${HOST_PORT}" API="http://127.0.0.1:${HOST_PORT}" STATE_DIR="$(mktemp -d)" +chmod 0777 "${STATE_DIR}" cleanup() { status=$? @@ -42,9 +43,10 @@ start_container() { docker run -d \ --name "${CONTAINER_NAME}" \ -p "${HOST_PORT}:${HOST_PORT}" \ - -v "${STATE_DIR}:/tmp/zero" \ + -v "${STATE_DIR}:/data" \ -e PORT="${HOST_PORT}" \ - -e ZERO_JOURNAL_PATH=/tmp/zero/decisions.jsonl \ + -e ZERO_JOURNAL_PATH=/data/decisions.jsonl \ + -e ZERO_FOUNDATION_STATE_DIR=/data/.zero \ -e ZERO_HYPERLIQUID_LIVE_PRICES=false \ -e ZERO_INTELLIGENCE_API_TOKEN=railway-intelligence-token \ -e ZERO_INTELLIGENCE_API_PLAN=team_fund \ diff --git a/scripts/railway_start.sh b/scripts/railway_start.sh index 3b445dd..36f2916 100755 --- a/scripts/railway_start.sh +++ b/scripts/railway_start.sh @@ -4,10 +4,13 @@ set -euo pipefail PORT="${PORT:-8765}" ZERO_JOURNAL_PATH="${ZERO_JOURNAL_PATH:-/data/decisions.jsonl}" ZERO_HYPERLIQUID_LIVE_PRICES="${ZERO_HYPERLIQUID_LIVE_PRICES:-true}" +ZERO_DEPLOYMENT_VERSION="${ZERO_DEPLOYMENT_VERSION:-engine-v0.1.6}" +ZERO_FOUNDATION_STATE_DIR="${ZERO_FOUNDATION_STATE_DIR:-/data/.zero}" ZERO_REPO_ROOT="${ZERO_REPO_ROOT:-/app}" mkdir -p "$(dirname "${ZERO_JOURNAL_PATH}")" -export ZERO_REPO_ROOT +mkdir -p "${ZERO_FOUNDATION_STATE_DIR}" +export ZERO_DEPLOYMENT_VERSION ZERO_FOUNDATION_STATE_DIR ZERO_REPO_ROOT args=( zero-paper-api diff --git a/scripts/railway_template_packet.py b/scripts/railway_template_packet.py index cd5b339..46b2643 100755 --- a/scripts/railway_template_packet.py +++ b/scripts/railway_template_packet.py @@ -12,10 +12,12 @@ ROOT = Path(__file__).resolve().parents[1] OUTPUT = ROOT / "contracts" / "distribution" / "railway-template.json" SCHEMA_VERSION = "zero.railway_template_packet.v1" -GENERATED_AT = "2026-05-05T07:51:46Z" +GENERATED_AT = "2026-07-01T08:05:00Z" PUBLIC_DEMO_URL = "https://zero-production-5214.up.railway.app" TEMPLATE_URL = "https://railway.com/deploy/zero-paper-runtime" TEMPLATE_ICON_URL = "https://avatars.githubusercontent.com/u/273590449.png" +RUNTIME_PACKAGE = "zero-engine==0.1.6" +RUNTIME_VERSION = "engine-v0.1.6" def text(path: str) -> str: @@ -48,6 +50,9 @@ def build_packet() -> dict[str, Any]: "template_docs_include_marketplace_overview": "Marketplace Overview Copy" in template_docs, "template_docs_name_marketplace_icon": TEMPLATE_ICON_URL in template_docs, "template_docs_name_icon": "docs/assets/zero-template-icon.svg" in template_docs, + "template_docs_name_runtime_package": RUNTIME_PACKAGE in template_docs, + "template_docs_name_claim_success_log": "claim_token_consumed=true" in template_docs, + "template_docs_name_bootstrap_readback": "runtime.foundation_bootstrap_*" in template_docs, "partner_docs_present": file_exists("docs/railway-partner.md"), "partner_docs_name_application_url": "https://railway.com/partners" in partner_docs, "partner_docs_name_template_url": TEMPLATE_URL in partner_docs, @@ -58,21 +63,28 @@ def build_packet() -> dict[str, Any]: "template_icon_present": file_exists("docs/assets/zero-template-icon.svg"), "railway_toml_present": file_exists("railway.toml"), "start_script_present": file_exists("scripts/railway_start.sh"), - "dockerfile_installs_engine": "python -m pip install --no-cache-dir /app/engine" - in dockerfile, + "dockerfile_installs_claim_capable_package": RUNTIME_PACKAGE in dockerfile, "railway_uses_dockerfile": 'builder = "DOCKERFILE"' in railway_toml, "railway_healthcheck_configured": 'healthcheckPath = "/health"' in railway_toml, "railway_start_uses_port": 'PORT="${PORT:-8765}"' in start_script, "railway_start_uses_durable_journal": ( 'ZERO_JOURNAL_PATH="${ZERO_JOURNAL_PATH:-/data/decisions.jsonl}"' in start_script ), + "railway_start_names_runtime_version": ( + f'ZERO_DEPLOYMENT_VERSION="${{ZERO_DEPLOYMENT_VERSION:-{RUNTIME_VERSION}}}"' + in start_script + ), + "railway_start_uses_foundation_state_dir": ( + 'ZERO_FOUNDATION_STATE_DIR="${ZERO_FOUNDATION_STATE_DIR:-/data/.zero}"' + in start_script + ), } return { "schema_version": SCHEMA_VERSION, "generated_at": GENERATED_AT, "summary": { - "status": "published_on_marketplace", + "status": "pending_marketplace_republish_after_runtime_package_update", "public_demo_url": PUBLIC_DEMO_URL, "template_url": TEMPLATE_URL, "template_name": "ZERO Paper Runtime", @@ -80,16 +92,23 @@ def build_packet() -> dict[str, Any]: "project": "fabulous-enchantment", "service": "zero", "environment": "production", - "verified_deployment_at_generation": "ea8424de-f5e3-4227-84a3-67970f5b0cfd", - "evidence_bundle_at_generation": "artifacts/deployment-evidence/20260504T192122Z", + "verified_deployment_at_generation": None, + "evidence_bundle_at_generation": None, "autodeploy_config": { "builder": "DOCKERFILE", "dockerfile_path": "Dockerfile", + "runtime_package": RUNTIME_PACKAGE, "healthcheck_path": "/health", "start_command": "/app/scripts/railway_start.sh", }, - "doctor_summary": {"ok": 17, "warn": 1, "fail": 0}, - "evidence_verify": {"ok": True, "checks": 60, "fail": 0}, + "doctor_summary": { + "status": "pending_after_marketplace_republish", + "reason": "0.1.6 claim-capable template source changed after the last live demo evidence bundle", + }, + "evidence_verify": { + "ok": None, + "status": "pending_after_marketplace_republish", + }, "partner_application": "template_published_partner_pending", "partner_application_packet": "docs/railway-partner.md", }, @@ -120,10 +139,14 @@ def build_packet() -> dict[str, Any]: "ZERO_MODE": "paper", "ZERO_JOURNAL_PATH": "/data/decisions.jsonl", "ZERO_HYPERLIQUID_LIVE_PRICES": "true", + "ZERO_DEPLOYMENT_VERSION": RUNTIME_VERSION, + "ZERO_FOUNDATION_STATE_DIR": "/data/.zero", "ZERO_INTELLIGENCE_STORE_PATH": "/data/zero/intelligence.jsonl", "ZERO_INTELLIGENCE_API_PLAN": "free", "ZERO_INTELLIGENCE_API_ACCOUNT_ID": "acct_railway", "ZERO_DEPLOYMENT_ID": "zero-railway-public-paper", + "ZERO_CONTROL_URL": "", + "ZERO_CLAIM_TOKEN": "", }, "publish_steps": [ "Published the linked Railway project as a template from the Railway dashboard.",