masterplan-lock / check (pull_request) Successful in 4s
masterplan-lock / check (push) Successful in 7s
CI (SIAX Cloud) / security (pull_request) Successful in 17s
CI (SIAX Cloud) / security (push) Successful in 18s
CI (SIAX Cloud) / quality (push) Successful in 39s
CI (SIAX Cloud) / integration (pull_request) Successful in 41s
CI (SIAX Cloud) / integration (push) Successful in 44s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 1m37s
Security Scan (SIAX Cloud) / security (push) Successful in 1m35s
CI (SIAX Cloud) / quality (pull_request) Successful in 3m4s
Two defects of the same kind: the repo asserted things about itself that were
not true, and nothing in it could tell.
A. CHANGELOG.md claimed CSP/HSTS/X-Frame-Options since 2026-08. `main`
contained none of it -- a repo-wide grep for
`content-security-policy|strict-transport-security|x-frame-options` matched
exactly one file, CHANGELOG.md itself. The only header on 0pt.app was
Vercel's own HSTS default, which does not survive the move to
Coolify/Traefik that siax.config.json and .gitea/workflows/cd.yml are
already pointed at.
- next.config.mjs: `headers()` on `/(.*)` with CSP (frame-ancestors 'none',
base-uri 'self', object-src 'none', form-action 'self'), HSTS,
X-Frame-Options DENY, X-Content-Type-Options, Referrer-Policy,
Permissions-Policy; plus poweredByHeader: false. Same idiom as
sax3l/siax-io's next.config.mjs.
- Carve-outs re-derived against THIS tree, not inherited: the stale
`ci/security-headers-and-scanning` branch (8d2ff73, 142 commits behind)
carried a `form-action https://checkout.stripe.com` allowance justified
by a redirect that is actually `window.location.href`, which form-action
does not govern -- dropped rather than kept as decorative permission.
frame-src is derived from NOCODB_BASE_URL/METABASE_SITE_URL, the same env
the components/embed.tsx iframe is configured from, so wiring that
component up cannot be silently blanked by this header (the failure
sax3l/siax-io recorded as SIO-013). 'unsafe-eval' is dev-only.
- tests/security/security-headers.test.ts: blocking regression gate inside
`pnpm run test`, i.e. the required "CI (SIAX Cloud) / quality" context.
Asserts against expectations written out in the test, not imported from
the config. Proven to fail: removing X-Frame-Options -> `pnpm run test`
exits 1; weakening frame-ancestors to 'self' -> exits 1.
B. scripts/siax-doctor.mjs validated that siax.repo.json fields were PRESENT,
never what they said. Measured on this tree: operationalState "running"
(false) and "down" (true) both gave 40/40, exit 0, byte-identical output.
- Value checks (enums, git.siax.io const, fleet node pattern, bare-hostname
form), cross-file consistency (declared prod hostnames must appear in
this repo's own siax.config.json / contracts/openapi.yaml, and vice
versa), and GOV-06 substantiation: operationalState must be backed by a
dated probe record in evidence/operational-state.json that agrees with
it. Liveness is not knowable offline; whether a liveness CLAIM is backed
is. Offline, deterministic, no network -- CI can never go red for want of
an internet connection.
- `--live` (opt-in) probes the declared hosts for real and checks the
headers on the wire; `--write-evidence` refreshes the record. Not wired
into CI on purpose.
- tests/repo/siax-doctor.test.ts runs the doctor inside `pnpm run test` and
asserts the negative cases against fixture trees (SIAX_DOCTOR_ROOT), so
the "cannot fail" defect cannot come back.
C. CHANGELOG.md is now true. The header claim stands; the four claims it was
bundled with (Trivy --exit-code 1, gitleaks, SHA-pinned actions, SBOM +
license artifacts) are still false on main and are listed as not
implemented instead of left asserted. All four live in .gitea/workflows/**,
a protected path on this repo, so they need an authorised workflow change.
Nothing under .gitea/workflows/** or .gitignore is touched.
Verified: pnpm run typecheck exit 0; pnpm run test exit 0 (74 files / 965
tests passed, up from 72/945); pnpm run build exit 0 and the built
.next/routes-manifest.json carries all six headers; node scripts/siax-doctor.mjs
55/55 exit 0; --live 57/57 exit 0. eslint on the new files exit 0 (repo lint is
advisory and its 17 pre-existing problems are unchanged).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
129 lines
7.2 KiB
Markdown
129 lines
7.2 KiB
Markdown
# Changelog
|
||
|
||
> BACKLOG P1-3. Format loosely follows [Keep a Changelog](https://keepachangelog.com/).
|
||
> No git tags/releases exist yet for this repo — this file starts the practice
|
||
> retroactively from real `git log` history rather than inventing version
|
||
> numbers that were never cut. Dates are commit dates on `main`, not calendar
|
||
> "release" dates (there have been none).
|
||
|
||
## [Unreleased]
|
||
|
||
- `feat(deploy)`: appen är nu faktiskt byggbar och körbar utanför Vercel.
|
||
`next.config.mjs` sätter `output: "standalone"`; Dockerfilen är omskriven
|
||
till samma form som `sax3l/siax-io` (som redan körs självhostat på flottan):
|
||
fyra steg, `node:22-alpine` (matchar CI:s nodversion, var 24), `libc6-compat`,
|
||
icke-root, HEALTHCHECK mot `/api/health`, npm/npx borttaget ur runtime-imagen.
|
||
Uppmätt: image 1,33 GB -> 423 MB. `--ignore-workspace` borttaget (dess
|
||
motivering var inaktuell sedan P1-21) så containerinstallationen är samma
|
||
kommando som CI kör; `--prod=false` tillagt eftersom Coolify sätter
|
||
NODE_ENV=production vid byggtid och pnpm då hoppar över devDependencies.
|
||
- `fix(images)`: `images.unoptimized: true` borttaget — det var v0-generatorns
|
||
standardvärde, inte ett val (`next.config.mjs` var orörd sedan initial commit).
|
||
Självhostad bildoptimering kräver att `sharp` faktiskt finns i runtime-imagen:
|
||
`next build` spårar den inte in i `.next/standalone`, och
|
||
`outputFileTracingIncludes` får Turbopack att panika på pnpms symlänkar. Sharp
|
||
levereras därför i ett eget npm-steg i Dockerfile, pinnat till versionen i
|
||
`pnpm-lock.yaml`. Verifierat i containern: `/_next/image` svarar 200 med
|
||
`image/avif` (1,46 MB PNG -> 6,7 kB).
|
||
- fix(security): response security headers are set by the app itself, in
|
||
`next.config.mjs` `headers()` — CSP (incl. `frame-ancestors 'none'`,
|
||
`base-uri 'self'`, `object-src 'none'`, `form-action 'self'`), HSTS,
|
||
X-Frame-Options `DENY`, X-Content-Type-Options, Referrer-Policy,
|
||
Permissions-Policy — plus `poweredByHeader: false`. Regression-gated by
|
||
`tests/security/security-headers.test.ts` (blocking, runs in
|
||
`pnpm run test`) and re-checkable against a live origin with
|
||
`node scripts/siax-doctor.mjs --live`. (BACKLOG P0-13, P1-15)
|
||
|
||
Correcting this file: from 2026-08 until 2026-09-07 this bullet also
|
||
claimed the headers themselves, while `main` contained none — a repo-wide
|
||
grep for `content-security-policy|strict-transport-security|
|
||
x-frame-options` matched only CHANGELOG.md. The only thing setting HSTS on
|
||
0pt.app was Vercel's platform default (still observable on the 402
|
||
responses recorded in `evidence/operational-state.json`), which is not this
|
||
repo's and does not survive the move to Coolify/Traefik. The claim is true
|
||
as of this entry; the four claims it was bundled with are not, and are
|
||
tracked in the section below.
|
||
|
||
- fix(repo-honesty): `scripts/siax-doctor.mjs` validated that
|
||
`siax.repo.json` fields were PRESENT, never what they said — measured:
|
||
`operationalState: "running"` (false at the time; every declared host
|
||
answered 402/404) and `"down"` (true) both produced 40/40 and
|
||
byte-identical output. It now validates values (enums, hostname form),
|
||
cross-file consistency (declared prod hostnames must appear in this repo's
|
||
own deploy/contract config, and vice versa), and requires
|
||
`operationalState` to be backed by a dated probe record in
|
||
`evidence/operational-state.json` that agrees with it. Offline and
|
||
deterministic by default; `--live` (opt-in) probes the real hosts.
|
||
Gated by `tests/repo/siax-doctor.test.ts`, which also asserts the negative
|
||
cases against fixture trees. (BACKLOG P1-19, control AGENT-04, GOV-06)
|
||
- fix(health): `/api/health` reports real per-dependency status instead of a
|
||
hardcoded `ok: true`; new `/api/ready` readiness endpoint. (BACKLOG P0-3)
|
||
- docs/chore: repo-hygiene pass — schema-valid `siax.repo.json`/`siax.config.json`
|
||
(standard v2.0), ADRs, this CHANGELOG, `siax:doctor` script, devcontainer,
|
||
`docs/architecture/ARCHITECTURE.md` §5 "Ytor och paritet", CODEOWNERS,
|
||
stale-link cleanup. (BACKLOG P1-1, P1-2, P1-3, P1-19, P1-20, P1-21, P2-5, P3-5)
|
||
|
||
### Claimed here before 2026-09-07 but NOT implemented — status 2026-09-15
|
||
|
||
These were asserted by the earlier `fix(security)` bullet and were not true on
|
||
`main` at that time. Current state (measured against `main`, 2026-09-15):
|
||
|
||
- CI security gates now ENFORCE: `security.yml` and `nightly.yml` run Trivy
|
||
with `--exit-code 1` (landed via `ci/make-required-security-gate-real`,
|
||
2026-09-08). (BACKLOG P0-16 — CLOSED)
|
||
- gitleaks is still not run anywhere in this repo. (BACKLOG P0-19 — OPEN)
|
||
- Actions are still NOT pinned to commit SHAs (`actions/checkout@v4` etc.).
|
||
(BACKLOG P1-14 — OPEN)
|
||
- No SBOM and no license inventory are produced or uploaded as CI artifacts.
|
||
(OPEN; the `fix/wire-signed-digest-to-deploy` branch carries a prepared
|
||
release chain that needs its cosign/registry secrets before it can land.)
|
||
|
||
## 2026-08-11 — Evasion-stack decision provenance
|
||
|
||
- Attributed and dated the account-owner's keep-decision on `services/scrapers/*`
|
||
across `SECURITY.md`, `CONTRIBUTING.md`, `prompts/002-compliance-audit.md`,
|
||
`renovate.json`, `.gitea/workflows/security.yml`, `docs/slo.md`.
|
||
|
||
## 2026-08-09/10 — Golden Standard compliance pass (FAS 0–10)
|
||
|
||
- `docs/compliance/{INVENTORY,RELATIONS,OSS-INVENTORY,GOLDEN-STANDARD-REPORT,BACKLOG,SLUTRAPPORT}.md`
|
||
written; `siax.repo.json` classification recorded.
|
||
- `fix(security)`: `GET /api/orders` and `GET /api/admin/metrics` now require
|
||
auth (were live 200 anonymous — the routes sit outside `middleware.ts`'s
|
||
`/api/0pt/:path*` matcher).
|
||
- `fix(deploy)`: `siax.config.json` `deploy.target` flipped from `vercel` to
|
||
`coolify` (config-only; the live instance is still on Vercel until a
|
||
Coolify app is provisioned and DNS is cut over).
|
||
- `feat(contract)`: OpenAPI 3.1 SSOT added (`contracts/openapi.yaml`);
|
||
`@vercel/analytics` and `next/font/google` removed in favor of self-hosted
|
||
`@fontsource-variable/*` (AR-4).
|
||
- `ci(workflows)`: CD/security/nightly split under `.gitea/workflows/`.
|
||
- README/SECURITY/CONTRIBUTING/AGENTS + runbooks (deploy, incident,
|
||
backup-restore, oncall) + SLO doc added.
|
||
|
||
## 2026-08 — Auth + capability hardening ("Wave 2/3")
|
||
|
||
- `fix(auth)`: `AUTH_ENFORCE` default flipped to fail-closed on `/api/0pt/*`.
|
||
- Zitadel Bearer-token verification wired into `middleware.ts`
|
||
(`src/auth/zitadel.ts`, `src/auth/context.ts`).
|
||
- P0 capability layer built out: `0pt.query_source`, document
|
||
capture/extraction, Iceberg write-boundary, external writeback, dataset
|
||
quality flow docs.
|
||
- CI quality gate added (typecheck/lint/test), `.env.example` committed
|
||
(previously blocked by a blanket `.gitignore` `.env*` rule).
|
||
|
||
## 2026-07-13 — Scraper stack (kept, see BACKLOG P3-3 / SECURITY.md)
|
||
|
||
- `services/scrapers/*` (WAF-bypass, proxy rotation, ClickHouse pipeline) was
|
||
added on the Gitea line of history. This is the subject of the dated,
|
||
attributed account-owner decision referenced throughout this repo's
|
||
compliance docs — not reopened or altered by this changelog entry.
|
||
|
||
## Earlier
|
||
|
||
- Initial Next.js scaffold, order/catalog/portal UI, FastAPI engine
|
||
scaffold, i18n (EN/SV) and accessibility passes, lakehouse write-path
|
||
ownership ADR (`ADR-0030`, in the `n0d` repo).
|
||
|
||
[Unreleased]: https://git.siax.io/sax3l/0pt/compare/main...HEAD
|