From 34a2953a44cab741bcb8d8a42753c069fb96f3ed Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Thu, 13 Aug 2026 16:04:44 +0700 Subject: [PATCH] fix(queen): deploy through Railway's own hook, not a pipeline that never worked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit queen-deploy.yml ran 35 times between 2026-04-01 and 2026-04-03 and succeeded zero times: 32 failures, 3 cancellations, every failure on the same "Build and Push to Railway" step. It could not have worked. It pushed an image without ever running `docker login`; it carried the project id 90c3f0c8-…-5e123abc4567, which is a placeholder rather than the real e6dfacdc-3eed-48ae-929b-02e87f07f0aa; its `test` job read `steps.deploy.outputs` from a different job; and on every run it posted "✅ Queen deployed" to issue 476 — a claim that was never once true. Railway builds from its own GitHub hook here, the way woody-weed-bot already ships, so the workflow no longer deploys. It proves the artefact builds and asserts the bundle was actually emitted, because a build that emits nothing still exits 0. What is deployed is apps/queen-web, and only that. The Queen's Zig backend does not exist as a buildable artefact: - deploy/Dockerfile.queen runs `zig build … queen-backend`. build.zig declares 67 steps and none is named that. The nearest, `libqueen`, builds the libtrinity-queen C API library for the SwiftUI dashboard, not a server — so /usr/local/bin/queen-backend is never produced. - the same Dockerfile copies /zig-linux-x86_64-0.15.2 out of a stage that extracted /zig-x86_64-linux-0.15.1 — different name order and different version. Only the latter name exists on ziglang.org; the former 404s. That COPY is why the build died every time. - src/server_main.zig, the nearest thing to a server, answers every request with one fixed JSON blob, is wired into no build step, binds 127.0.0.1, and says "for fly.io" in its header. Dockerfile.queen is therefore left untouched and still broken; it is the backend's problem to fix, not this change's. The brain has no vehicle yet for the same reason: it is Zig, and it would ship inside that backend. Two rules were being broken and are now honoured. The runtime entrypoint is no longer `sh -c` (no-shell-scripts: entrypoints are binaries, and this path has no custom container at all), and the container carries SOUL.md as the Queen Trinity Orchestrator Law requires. That law was impossible to obey. .gitignore:315 ignores *.md and re-included only templates/SOUL.md, so a container's own soul could never be committed without -f. A `!**/SOUL.md` negation fixes the cause rather than forcing this one file past it. Measured before claiming: 1844 .md on disk against 1803 tracked, so the existing !docs/** and !.github/ negations already cover almost everything — the gap was narrow, not the whole documentation tree. Verified rather than assumed: queen-web builds (exit 0, 253 kB JS), and `PORT=7311 npm start` answers HTTP 200 on the machine's LAN address, not just localhost — which proves the 0.0.0.0 bind Railway needs, since a loopback bind is unreachable from outside the container. --- Королева деплоится через собственный хук Railway, а не через пайплайн, который не работал ни разу. Старый queen-deploy.yml запускался 35 раз и не преуспел ни разу: 32 провала и 3 отмены, все на шаге сборки. Он и не мог работать — пуш образа без docker login, плейсхолдер вместо ID проекта, чтение outputs из чужого джоба и комментарий «✅ Queen deployed» в issue 476 на каждом провале. Теперь Railway собирает сам, а workflow только доказывает, что артефакт собирается. Выкатывается apps/queen-web и только он: Zig-бэкенда королевы не существует как собираемого артефакта — шага queen-backend нет среди 67 шагов build.zig, а Dockerfile копирует несуществующий каталог Zig. Проверено замером: сборка exit 0, старт отвечает HTTP 200 на сетевом адресе машины, что доказывает бинд 0.0.0.0. Co-Authored-By: Claude Opus 5 --- .github/workflows/queen-deploy.yml | 104 +++++++++++++---------------- .gitignore | 5 ++ apps/queen-web/SOUL.md | 84 +++++++++++++++++++++++ apps/queen-web/package.json | 3 +- apps/queen-web/railway.json | 14 ++++ 5 files changed, 151 insertions(+), 59 deletions(-) create mode 100644 apps/queen-web/SOUL.md create mode 100644 apps/queen-web/railway.json diff --git a/.github/workflows/queen-deploy.yml b/.github/workflows/queen-deploy.yml index 76e4791e96..c46dad3719 100644 --- a/.github/workflows/queen-deploy.yml +++ b/.github/workflows/queen-deploy.yml @@ -1,72 +1,60 @@ -name: Queen Self-Improving Container +name: Queen — build check + +# Railway deploys the Queen itself, from its own GitHub hook, the same way +# woody-weed-bot ships. This workflow therefore does NOT deploy: it only proves +# the artefact Railway is about to build actually builds. +# +# It replaces a workflow that pushed a Docker image and called a Railway +# GraphQL mutation. That version ran 35 times between 2026-04-01 and +# 2026-04-03 and succeeded zero times — 32 failures and 3 cancellations, every +# one of them on the same "Build and Push to Railway" step. It could not have +# worked: it pushed to a registry without ever running `docker login`, it +# carried a placeholder project id (90c3f0c8-…-5e123abc4567) instead of the +# real one (e6dfacdc-3eed-48ae-929b-02e87f07f0aa), its `test` job read +# `steps.deploy.outputs` from a different job, and on every run it posted +# "✅ Queen deployed" to issue 476 — a claim that was never once true. +# +# Railway собирает Королеву сам, по своему GitHub-хуку. Этот workflow ничего +# не деплоит — он только доказывает, что артефакт действительно собирается. on: push: branches: [main] - paths: [apps/queen-web/**, src/tri/queen/**, deploy/Dockerfile.queen] + paths: + - 'apps/queen-web/**' + - '.github/workflows/queen-deploy.yml' + pull_request: + paths: + - 'apps/queen-web/**' workflow_dispatch: -env: - RAILWAY_PROJECT_ID: 90c3f0c8-c26e-4c77-9e45-5e123abc4567 - jobs: - deploy: + build: runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Configure Railway - run: | - echo "RAILWAY_API_TOKEN=${{ secrets.RAILWAY_TOKEN }}" >> $GITHUB_ENV - echo "RAILWAY_PROJECT_ID=${{ env.RAILWAY_PROJECT_ID }}" >> $GITHUB_ENV - - - name: Build and Push to Railway - run: | - # Build Queen container image - docker build -f deploy/Dockerfile.queen -t registry.${{ env.RAILWAY_PROJECT_ID }}.railway.app/queen:latest . - - # Push to Railway registry - docker push registry.${{ env.RAILWAY_PROJECT_ID }}.railway.app/queen:latest + defaults: + run: + working-directory: apps/queen-web - - name: Deploy to Railway - run: | - # Trigger Railway deployment via API - curl -X POST \ - -H "Authorization: Bearer ${{ secrets.RAILWAY_TOKEN }}" \ - -H "Content-Type: application/json" \ - https://backboard.railway.app/graphql/v2 \ - -d '{ - "query": "mutation { deployService(input: { projectId: \"'"$RAILWAY_PROJECT_ID"'\", serviceName: \"queen\" }) { id } }" - }' + steps: + - uses: actions/checkout@v4 - - name: Get Deployment URL - run: | - # Get service URL from Railway - SERVICE_URL=$(curl -s -X POST \ - -H "Authorization: Bearer ${{ secrets.RAILWAY_TOKEN }}" \ - -H "Content-Type: application/json" \ - https://backboard.railway.app/graphql/v2 \ - -d '{ - "query": "query { project(id: \"'"$RAILWAY_PROJECT_ID"'\") { services { name url } } }" - }' | jq -r '.data.project.services[] | select(.name == "queen") | .url') + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + cache-dependency-path: apps/queen-web/package-lock.json - echo "Queen deployed to: $SERVICE_URL" - echo "queen_url=$SERVICE_URL" >> $GITHUB_OUTPUT + - name: Install + run: npm ci - - name: Comment on Issue - if: github.event_name == 'push' - run: | - gh issue comment 476 --body "✅ Queen deployed to ${{ steps.deploy.outputs.queen_url }}" + - name: Build + run: npm run build - test: - runs-on: ubuntu-latest - needs: deploy - steps: - - name: Test Health Endpoint + # A build that emits nothing still exits 0. Assert the artefact exists, + # so this check cannot go green on an empty dist. + - name: Assert the bundle was actually emitted run: | - # Wait for deployment to be ready - sleep 30 - - # Test health endpoint - curl -f ${{ steps.deploy.outputs.queen_url }}/health || exit 1 + test -f dist/index.html || { echo "dist/index.html missing"; exit 1; } + test -n "$(find dist/assets -name '*.js' -print -quit)" || { echo "no JS bundle emitted"; exit 1; } + echo "emitted:" + ls -la dist dist/assets diff --git a/.gitignore b/.gitignore index 7849bb1246..7190a23287 100644 --- a/.gitignore +++ b/.gitignore @@ -316,6 +316,11 @@ fpga/tools/weight_pack_wide !CLAUDE.md !.github/ !docs/** +# Queen Trinity Orchestrator Law: every container MUST carry SOUL.md at +# its root. The *.md rule above made that impossible to honour -- only +# templates/SOUL.md was re-included, so a container's own soul could never +# be committed without -f. +!**/SOUL.md # Docker files in root (use deploy/ subdirectory instead) Dockerfile* diff --git a/apps/queen-web/SOUL.md b/apps/queen-web/SOUL.md new file mode 100644 index 0000000000..3ced0f119c --- /dev/null +++ b/apps/queen-web/SOUL.md @@ -0,0 +1,84 @@ +# SOUL.md — Agent Soul Binding + +**Law**: Every container/agent MUST have `SOUL.md` at root. + +--- + +## Agent Identity + +| Field | Value | +|-------|--------| +| **Agent Type** | Custom — Queen web surface (no autonomous loop) | +| **Agent ID** | `queen-web` | +| **Bound Issue** | `#476` | + +--- + +## Mission + +```markdown +Serve the Queen dashboard as a static surface on Railway. + +This container carries the React UI only. It executes nothing on the user's +behalf, holds no credentials, and makes no autonomous decisions — so the +8-step agent cycle, the MNL pattern and Akashic journaling do not apply to it. +It is bound to issue #476 because that is the issue the Queen deployment has +always been reported against. +``` + +--- + +## Allowed Commands + +```markdown +npm ci — install from the committed lockfile +npm run build — emit dist/ +npm start — vite preview --host 0.0.0.0 --port $PORT + +Nothing else. This container has no shell entrypoint and no write access to +the Trinity tree. +``` + +--- + +## Stop Conditions + +```markdown +- The build emits no dist/index.html or no JS bundle → fail, do not deploy. +- The process binds anything other than 0.0.0.0:$PORT → fail; Railway routes + to $PORT and a loopback bind is unreachable from outside the container. +``` + +--- + +## Reporting Format + +Protocol v2, on issue #476. + +`✅ [DONE]` may only be posted after the deployed URL has answered. The +workflow this replaced posted "✅ Queen deployed" on every run, including all +32 failures — the claim was never once verified against a live URL. + +--- + +## Scope — what this is NOT + +The Queen's Zig backend is **not** in this container and does not currently +exist as a buildable artefact: + +- `build.zig` declares 67 steps; none is named `queen-backend`, which + `deploy/Dockerfile.queen` invokes. The closest is `libqueen`, and it builds + the `libtrinity-queen` C API library for the SwiftUI dashboard, not a server. +- `src/server_main.zig` is a 48-line stub that answers every request with the + same JSON, is wired into no build step, and binds `127.0.0.1`. + +The brain (`src/brain`) therefore has no vehicle yet either: it is Zig, and it +would ship inside that backend once the backend exists. + +--- + +## References + +- `CLAUDE.md` — Queen Trinity Orchestrator Law +- `AGENTS.md` +- `.claude/rules/no-shell-scripts.md` — why the entrypoint is not `sh -c` diff --git a/apps/queen-web/package.json b/apps/queen-web/package.json index 23df47b672..c45f62fdc2 100644 --- a/apps/queen-web/package.json +++ b/apps/queen-web/package.json @@ -5,7 +5,8 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "start": "vite preview --host 0.0.0.0 --port ${PORT:-8080}" }, "dependencies": { "react": "^19.0.0", diff --git a/apps/queen-web/railway.json b/apps/queen-web/railway.json new file mode 100644 index 0000000000..90040c707d --- /dev/null +++ b/apps/queen-web/railway.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://railway.com/railway.schema.json", + "build": { + "builder": "NIXPACKS", + "buildCommand": "npm ci && npm run build" + }, + "deploy": { + "startCommand": "npm start", + "healthcheckPath": "/", + "healthcheckTimeout": 30, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 3 + } +}