From d7a1c4d5e1cc5d8775454a1ecfcbfcdbb10564d8 Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Sun, 6 Sep 2026 09:56:53 +0200 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20the=20Coolify=20deploy=20hook=20is?= =?UTF-8?q?=20LIVE=20=E2=80=94=20six=20docs=20still=20say=20it=20is=20a=20?= =?UTF-8?q?placeholder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six documents on main assert that no Coolify app has been provisioned for 0pt and that cd.yml's COOLIFY_APP_ID is a literal placeholder whose guard fails the job on purpose. That is no longer true, and the gap is not cosmetic: it means a merge to main silently ships to production while the repo's own docs promise it cannot. Measured, not assumed: * repo Actions secret COOLIFY_TOKEN exists, created 2026-08-31T11:34:34Z (GET /api/v1/repos/sax3l/0pt/actions/secrets) * repo Actions variable COOLIFY_APP_ID exists with a real 24-character value, not the "TODO-no-coolify-app-provisioned-yet" literal (GET /api/v1/repos/sax3l/0pt/actions/variables) * both guards in .gitea/workflows/cd.yml therefore pass; on the push to main at 1ff34410 (2026-09-06) the job "Deploy to Coolify / deploy" (run 11695, job 26295) finished SUCCESS with "coolify deploy HTTP 200" in its log Deliberately NOT claimed: that the Coolify app builds, starts or serves, or that 0pt.app DNS points at it rather than Vercel. Nobody probed the running site, and every corrected passage says so explicitly rather than replacing one unverified claim with another. Corrected: README.md "Deploy", docs/02-architecture.md hosting callout, docs/architecture/decisions/0001 (consequence annotated, decision untouched -- an ADR is a historical record), docs/runbook/incident.md (the symptom row is now inverted: the risk is an unintended deploy, not a red job), docs/compliance/GOLDEN-STANDARD-REPORT.md D4 (❌ -> ⚠️, not ✅, because "webhook accepted" is not "app serves"), docs/compliance/INVENTORY.md CI row. Falsifiable check over all six files (stale claim present without a correction marker = red): origin/main -> FAIL (12 findings) this branch -> PASS mutation: drop only the README bullet -> FAIL mutation: drop only the runbook correction -> FAIL The check initially passed vacuously on docs/02-architecture.md because PR #32 had already put the string "CORRECTED 2026-09-06" in that file for an unrelated reason; the marker was made specific so the check could actually fail there too. Docs only. No workflow files, no .gitignore, no source. Co-Authored-By: Claude Opus 5 --- README.md | 15 +++++++++++++-- docs/02-architecture.md | 14 ++++++++++++-- .../0001-deploy-target-coolify-not-vercel.md | 7 +++++++ docs/compliance/GOLDEN-STANDARD-REPORT.md | 2 +- docs/compliance/INVENTORY.md | 2 +- docs/runbook/incident.md | 2 +- 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b0118ea..a40a8c3 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,25 @@ cd services/api && pip install -r requirements.txt && uvicorn orchestrator.main: (already committed, see [`Dockerfile`](Dockerfile)) plus [`.gitea/workflows/cd.yml`](.gitea/workflows/cd.yml) (renamed from `deploy.yml` 2026-08-10) that POSTs a restart to a Coolify app UUID on `COOLIFY_TOKEN` push. -- **Actually live right now:** the only running instance of `0pt.app` is still on **Vercel** +- ~~**Actually live right now:** the only running instance of `0pt.app` is still on **Vercel** (project `0pt`, scope *Simon Axelsson's projects*) — no Coolify app has been provisioned for this repo yet, so `deploy.yml`'s app UUID is a placeholder that fails the job on purpose until - someone fills it in. Flipping the config declares intent; it does not move traffic. See + someone fills it in. Flipping the config declares intent; it does not move traffic.~~ See [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) §5/§8 for the full "Vercel or fleet" state and what's still open (Coolify app provisioning + DNS cutover — a separate decision from this config change, since the live Vercel deployment is not being touched). +- **CORRECTED 2026-09-06 — the bullet above is no longer true. The Coolify deploy hook is LIVE.** + The repo secret `COOLIFY_TOKEN` exists (created 2026-08-31T11:34:34Z) and the repo Actions + variable `COOLIFY_APP_ID` holds a real 24-character UUID, **not** the + `TODO-no-coolify-app-provisioned-yet` literal. Both guard conditions in + [`.gitea/workflows/cd.yml`](.gitea/workflows/cd.yml) therefore pass. Measured on the push to + `main` at `1ff34410` (2026-09-06): job `Deploy to Coolify / deploy` (run 11695, job 26295) + finished **success** with `coolify deploy HTTP 200` in its log. **Every merge into `main` now + fires a real redeploy against `cloud.siax.io` — know what you are shipping before you merge.** + Still *not* verified by anyone: whether that Coolify app actually builds, starts and serves, and + whether `0pt.app` DNS points at it or still at Vercel. Nobody has probed the running site, so + treat "which host answers `0pt.app`" as an open question rather than assuming either answer. - **Engine + DB + embeds** → one VPS via [`infra/docker-compose.yml`](infra/docker-compose.yml) (Postgres+pgvector, Redis, api, worker, NocoDB, Metabase, Caddy). - Env inventory: [`.env.example`](.env.example). - **Auth on `/api/0pt/*` is fail-closed**: with `AUTH_ENFORCE` unset the middleware diff --git a/docs/02-architecture.md b/docs/02-architecture.md index 69a392e..20b0f8d 100644 --- a/docs/02-architecture.md +++ b/docs/02-architecture.md @@ -207,12 +207,22 @@ Flagged so it is not repeated: `src/services/quality-check-service.ts`, `src/ser > **SUPERSEDED 2026-08-11.** This section describes the original Vercel+Neon > hosting plan. `siax.config.json` flipped `deploy.target` from `"vercel"` to > `"coolify"` on 2026-08-09 to match every other SIAX Cloud OS repo — that -> decision supersedes the Vercel prescription below. The live instance is +> decision supersedes the Vercel prescription below. ~~The live instance is > **still actually running on Vercel today** (no Coolify app has been > provisioned yet — see `README.md`'s "Deploy" section and -> `docs/architecture/ARCHITECTURE.md` §5/§8 for the current, verified state); +> `docs/architecture/ARCHITECTURE.md` §5/§8 for the current, verified state)~~; > this section is kept for history, not as the current target. Do not read > "Vercel (Hobby/free)" below as still-current guidance. +> +> **CORRECTED 2026-09-06 — the struck sentence above is no longer true.** The Coolify +> deploy hook is live: repo secret `COOLIFY_TOKEN` exists (created 2026-08-31T11:34:34Z) +> and repo Actions variable `COOLIFY_APP_ID` holds a real 24-character UUID, not the +> `TODO-no-coolify-app-provisioned-yet` literal, so both guards in +> `.gitea/workflows/cd.yml` pass. On the push to `main` at `1ff34410` (2026-09-06) the +> `Deploy to Coolify / deploy` job finished **success** with `coolify deploy HTTP 200`. +> **Every merge into `main` fires a real redeploy against cloud.siax.io.** What is still +> unverified: whether that Coolify app builds and serves, and whether `0pt.app` DNS points +> at it or still at Vercel — nobody has probed the running site. - **Vercel** (Hobby/free) — Next.js + custom domain `0pt.app`. - **One small VPS** (e.g. Hetzner ~€5/mo) running `docker-compose` (`infra/docker-compose.yml`): FastAPI, workers, NocoDB, Metabase, (Postgres or use Neon), optional Meilisearch later. Per-service managed platforms (Railway) get expensive once you have 4–5 always-on services; a single VPS + Vercel + R2 lands ≈ <€10/mo to start. diff --git a/docs/architecture/decisions/0001-deploy-target-coolify-not-vercel.md b/docs/architecture/decisions/0001-deploy-target-coolify-not-vercel.md index a7fb772..ca09bfe 100644 --- a/docs/architecture/decisions/0001-deploy-target-coolify-not-vercel.md +++ b/docs/architecture/decisions/0001-deploy-target-coolify-not-vercel.md @@ -27,6 +27,13 @@ Coolify app UUID on push to `main`) both exist and are committed. `cd.yml`'s `COOLIFY_APP_ID` is a literal placeholder that fails the job loudly on purpose (see the guard step) rather than posting to a bogus endpoint. The live instance of `0pt.app` is still served by Vercel. + + > **UPDATE 2026-09-06 (the consequence above has since been overtaken; the + > decision itself stands).** **CORRECTED 2026-09-06 — this is no longer true.** The Coolify deploy hook is LIVE: the repo secret `COOLIFY_TOKEN` exists (created 2026-08-31T11:34:34Z) and the repo Actions variable `COOLIFY_APP_ID` holds a real 24-character UUID, not the `TODO-no-coolify-app-provisioned-yet` literal. Both guard conditions in `.gitea/workflows/cd.yml` therefore pass, and the push to `main` at `1ff34410` (2026-09-06) ran `Deploy to Coolify / deploy` to **success** with `coolify deploy HTTP 200` in the job log. **Every merge into `main` now fires a real redeploy against cloud.siax.io.** What is still NOT verified: whether that Coolify app actually builds and serves, and whether `0pt.app` DNS points at it or still at Vercel — nobody has probed the running site. + > + > An ADR is a historical record, so the original consequence text is left + > intact above rather than rewritten — read it as "true when written + > 2026-08-09", not as current state. - Flipping the config was deliberately decoupled from the actual cutover: it states intent (BACKLOG P0-2 / P3-1 own the "provision the Coolify app, cut DNS, decommission Vercel" work, gated on a dump-before-write blocker per diff --git a/docs/compliance/GOLDEN-STANDARD-REPORT.md b/docs/compliance/GOLDEN-STANDARD-REPORT.md index 30ef03b..d4c7c42 100644 --- a/docs/compliance/GOLDEN-STANDARD-REPORT.md +++ b/docs/compliance/GOLDEN-STANDARD-REPORT.md @@ -56,7 +56,7 @@ | D1 | Container icke-root, multi-stage, utan tsx i prod | ✅ | Dockerfile: node:24-alpine, builder+runtime, `USER node`, `CMD next start` (ingen tsx) | | D2 | /health verifierar beroenden | ⚠️ | /api/health rapporterar db/queue lägen men alltid `ok:true` — liveness ej readiness; "alltid grönt för en döende instans" (ARCHITECTURE §6) | | D3 | CI grön på main | ✅ | Gitea Actions run 3759 success; deploy.yml röd men avsiktligt (guard) | -| D4 | Deploy till Coolify provisionerad | ❌ | Coolify-app ej provisionerad; deploy.yml placeholder-UUID (avsiktligt röd); node:null | +| D4 | Deploy till Coolify provisionerad | ⚠️ **(uppdaterad 2026-09-06, var ❌)** | Coolify-app ÄR provisionerad: repo-variabeln `COOLIFY_APP_ID` innehåller en riktig 24-teckens-UUID och repo-secreten `COOLIFY_TOKEN` finns (skapad 2026-08-31). Guarden i `cd.yml` passerar och deployen körde `coolify deploy HTTP 200` på push `1ff34410`. Kvarstår ⚠️ och inte ✅ eftersom ingen har verifierat att appen faktiskt bygger, startar och serverar `0pt.app` — bara att webhooken accepteras | | D5 | Backup + testad restore | ❌ | Ingen backup, ingen DB i prod, restore aldrig testat (ARCHITECTURE §6) | | D6 | Loggning/observabilitet | ⚠️ | Ingen strukturerad loggning/OTel; endast console.warn (ARCHITECTURE §6) | | D7 | Infisical-projekt + token tillgängligt för denna session | ➖ | **Blockerare:** ingen Infisical-token tillhandahållen — projekt/secret-bootstrap kan inte verifieras (se SLUTRAPPORT) | diff --git a/docs/compliance/INVENTORY.md b/docs/compliance/INVENTORY.md index ccb0a57..e18ce0a 100644 --- a/docs/compliance/INVENTORY.md +++ b/docs/compliance/INVENTORY.md @@ -114,7 +114,7 @@ v0-datahub, prt-* etc.) finns i 0pt. | /api/health | 200, `{"ok":true,"db":"not_configured","queue":"not_configured"}` | live-probe 2026-08-10 | | /api/0pt (anonym) | **200 anonymt** — middleware fail-closed verkar INTE köra i prod (lucka S1) | live-probe 2026-08-10 | | /api/orders (anonym) | **200 anonymt** — inget skyddat (lucka S3) | live-probe 2026-08-10 | -| CI (Gitea Actions) | senaste ci.yml på main: **success** (run 3759, 2026-08-09). deploy.yml: röd men **avsiktligt** (guard: placeholder-app-ID). 48 runs totalt | Gitea API `/actions/runs` | +| CI (Gitea Actions) | senaste ci.yml på main: **success** (run 3759, 2026-08-09). ~~deploy.yml: röd men **avsiktligt** (guard: placeholder-app-ID)~~ **korrigerat 2026-09-06:** cd.yml är **grön** och deployar på riktigt — `COOLIFY_APP_ID` är en riktig UUID, guarden passerar, `coolify deploy HTTP 200` (run 11695, jobb 26295, head `1ff34410`). 48 runs totalt vid ursprungsmätningen | Gitea API `/actions/runs`, `/actions/variables`, `/actions/secrets` | | Secrets | Inga hårdkodade hemligheter hittade (grep-skannat). **Infisical ej inkopplat** — inga referenser i repot | grep + ARCHITECTURE §5 | | Backup | Ingen (ingen DB i prod att backa upp) | ARCHITECTURE §6 | diff --git a/docs/runbook/incident.md b/docs/runbook/incident.md index a442e9f..5adac4d 100644 --- a/docs/runbook/incident.md +++ b/docs/runbook/incident.md @@ -14,7 +14,7 @@ |---|---|---| | `/api/health` grön men `/api/orders` tom | Ingen DB i prod (in-memory-store) | Sätt `DATABASE_URL`; se BACKLOG P3-4 | | `/api/0pt/*` svarar 200 anonymt | Middleware körs inte på Vercel-deployen (lucka S1) | Rotorsaksanalys; verifiera 401 anonymt före "skyddad" | -| deploy.yml-röd på main | Guard: placeholder `COOLIFY_APP_ID` (avsiktligt) | Provisionera Coolify-app + fyll UUID | +| ~~deploy.yml-röd på main~~ | ~~Guard: placeholder `COOLIFY_APP_ID` (avsiktligt)~~ **INAKTUELL 2026-09-06** — guarden passerar numera; `COOLIFY_APP_ID` är en riktig UUID och `COOLIFY_TOKEN` finns | Rätt symtom i dag är det omvända: **varje merge till `main` utlöser en riktig omdeploy** mot cloud.siax.io (verifierat: push `1ff34410`, jobb `Deploy to Coolify / deploy` = success, `coolify deploy HTTP 200`). Vet vad du deployar innan du mergar | | Test `*.realinfra.test.ts` skip | ST0RE inte nåbar från CI | Inte en incident — förväntat beteende | | Trivy-fynd på `services/scrapers/*` | Medvetet behållen bypass-stack | Läs GOLDEN-STANDARD-REPORT C6; triage, radera ej | -- 2.54.0 From 853f09320c40af29874a07d1a2e6d5f12441958c Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Sun, 6 Sep 2026 10:01:17 +0200 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20probed=200pt.app=20=E2=80=94=20Verc?= =?UTF-8?q?el=20answers=20with=20HTTP=20402=20DEPLOYMENT=5FDISABLED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit said "nobody has probed the running site". I then probed it, so the docs should not keep hedging. curl -sI https://0pt.app/ HTTP/2 402 server: Vercel x-vercel-error: DEPLOYMENT_DISABLED strict-transport-security: max-age=63072000 https://0pt.app/api/health -> HTTP 402, body "Payment required / DEPLOYMENT_DISABLED" Two facts hold simultaneously and the docs now state both: the Coolify deploy hook fires for real on every push to main, AND the public product domain is dark behind a disabled Vercel deployment with DNS never cut over. Neither cancels the other. Still not established, and still marked as such: whether the Coolify app builds and starts. A deploy endpoint returning 200 means the request was accepted, not that a container came up healthy. Side note worth recording for PR #19: the HSTS header on that 402 response is Vercel's platform default, which is exactly the disappearing-on-cutover risk next.config.mjs in PR #19 was written to remove. Co-Authored-By: Claude Opus 5 --- README.md | 6 +++--- docs/02-architecture.md | 10 +++++++--- docs/compliance/GOLDEN-STANDARD-REPORT.md | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a40a8c3..86d318c 100644 --- a/README.md +++ b/README.md @@ -74,9 +74,9 @@ cd services/api && pip install -r requirements.txt && uvicorn orchestrator.main: `main` at `1ff34410` (2026-09-06): job `Deploy to Coolify / deploy` (run 11695, job 26295) finished **success** with `coolify deploy HTTP 200` in its log. **Every merge into `main` now fires a real redeploy against `cloud.siax.io` — know what you are shipping before you merge.** - Still *not* verified by anyone: whether that Coolify app actually builds, starts and serves, and - whether `0pt.app` DNS points at it or still at Vercel. Nobody has probed the running site, so - treat "which host answers `0pt.app`" as an open question rather than assuming either answer. + **Probed 2026-09-06 08:00 UTC:** `0pt.app` still answers from **Vercel** (`server: Vercel`, `x-vercel-id: arn1::…`) and returns **HTTP 402 `DEPLOYMENT_DISABLED` — "Payment required"** on both `/` and `/api/health`. So the public domain is *down*, DNS has **not** been cut over to Coolify, and the Coolify app — whatever state it is in — serves no `0pt.app` traffic. Two separate facts, both true at once: the deploy hook fires for real, and the product domain is dark behind an unpaid Vercel deployment. + Still not established: whether the Coolify app itself builds and starts — the deploy endpoint + returning 200 only means the request was accepted, not that a container came up healthy. - **Engine + DB + embeds** → one VPS via [`infra/docker-compose.yml`](infra/docker-compose.yml) (Postgres+pgvector, Redis, api, worker, NocoDB, Metabase, Caddy). - Env inventory: [`.env.example`](.env.example). - **Auth on `/api/0pt/*` is fail-closed**: with `AUTH_ENFORCE` unset the middleware diff --git a/docs/02-architecture.md b/docs/02-architecture.md index 20b0f8d..cd987e1 100644 --- a/docs/02-architecture.md +++ b/docs/02-architecture.md @@ -220,9 +220,13 @@ Flagged so it is not repeated: `src/services/quality-check-service.ts`, `src/ser > `TODO-no-coolify-app-provisioned-yet` literal, so both guards in > `.gitea/workflows/cd.yml` pass. On the push to `main` at `1ff34410` (2026-09-06) the > `Deploy to Coolify / deploy` job finished **success** with `coolify deploy HTTP 200`. -> **Every merge into `main` fires a real redeploy against cloud.siax.io.** What is still -> unverified: whether that Coolify app builds and serves, and whether `0pt.app` DNS points -> at it or still at Vercel — nobody has probed the running site. +> **Every merge into `main` fires a real redeploy against cloud.siax.io.** +> +> Probed 2026-09-06 08:00 UTC: `0pt.app` still answers from **Vercel** (`server: Vercel`) +> with **HTTP 402 `DEPLOYMENT_DISABLED`** on both `/` and `/api/health`. The public domain +> is therefore *down*, DNS has not been cut over, and the Coolify app serves no `0pt.app` +> traffic. Still unestablished: whether the Coolify app builds and starts at all — an +> accepted deploy request is not a healthy container. - **Vercel** (Hobby/free) — Next.js + custom domain `0pt.app`. - **One small VPS** (e.g. Hetzner ~€5/mo) running `docker-compose` (`infra/docker-compose.yml`): FastAPI, workers, NocoDB, Metabase, (Postgres or use Neon), optional Meilisearch later. Per-service managed platforms (Railway) get expensive once you have 4–5 always-on services; a single VPS + Vercel + R2 lands ≈ <€10/mo to start. diff --git a/docs/compliance/GOLDEN-STANDARD-REPORT.md b/docs/compliance/GOLDEN-STANDARD-REPORT.md index d4c7c42..6de0bcf 100644 --- a/docs/compliance/GOLDEN-STANDARD-REPORT.md +++ b/docs/compliance/GOLDEN-STANDARD-REPORT.md @@ -56,7 +56,7 @@ | D1 | Container icke-root, multi-stage, utan tsx i prod | ✅ | Dockerfile: node:24-alpine, builder+runtime, `USER node`, `CMD next start` (ingen tsx) | | D2 | /health verifierar beroenden | ⚠️ | /api/health rapporterar db/queue lägen men alltid `ok:true` — liveness ej readiness; "alltid grönt för en döende instans" (ARCHITECTURE §6) | | D3 | CI grön på main | ✅ | Gitea Actions run 3759 success; deploy.yml röd men avsiktligt (guard) | -| D4 | Deploy till Coolify provisionerad | ⚠️ **(uppdaterad 2026-09-06, var ❌)** | Coolify-app ÄR provisionerad: repo-variabeln `COOLIFY_APP_ID` innehåller en riktig 24-teckens-UUID och repo-secreten `COOLIFY_TOKEN` finns (skapad 2026-08-31). Guarden i `cd.yml` passerar och deployen körde `coolify deploy HTTP 200` på push `1ff34410`. Kvarstår ⚠️ och inte ✅ eftersom ingen har verifierat att appen faktiskt bygger, startar och serverar `0pt.app` — bara att webhooken accepteras | +| D4 | Deploy till Coolify provisionerad | ⚠️ **(uppdaterad 2026-09-06, var ❌)** | Coolify-app ÄR provisionerad: repo-variabeln `COOLIFY_APP_ID` innehåller en riktig 24-teckens-UUID och repo-secreten `COOLIFY_TOKEN` finns (skapad 2026-08-31). Guarden i `cd.yml` passerar och deployen körde `coolify deploy HTTP 200` på push `1ff34410`. Kvarstår ⚠️ och inte ✅: probe 2026-09-06 08:00 UTC visar att `0pt.app` fortfarande svarar från **Vercel** med **HTTP 402 `DEPLOYMENT_DISABLED`** — publika domänen är nere, DNS är inte omlagd, och Coolify-appen serverar ingen `0pt.app`-trafik. Att webhooken accepteras (HTTP 200) betyder inte att en container kommer upp frisk | | D5 | Backup + testad restore | ❌ | Ingen backup, ingen DB i prod, restore aldrig testat (ARCHITECTURE §6) | | D6 | Loggning/observabilitet | ⚠️ | Ingen strukturerad loggning/OTel; endast console.warn (ARCHITECTURE §6) | | D7 | Infisical-projekt + token tillgängligt för denna session | ➖ | **Blockerare:** ingen Infisical-token tillhandahållen — projekt/secret-bootstrap kan inte verifieras (se SLUTRAPPORT) | -- 2.54.0