docs+chore: repo hygiene -- v2.0 manifests, ADRs, changelog, doctor, guides #20
@@ -0,0 +1,12 @@
|
||||
# BACKLOG P6 (CI-16): lets a developer run `act` to execute .gitea/workflows
|
||||
# (GitHub-Actions-compatible syntax) locally before pushing, instead of
|
||||
# discovering a workflow bug only after a Gitea runner picks it up.
|
||||
# https://github.com/nektos/act
|
||||
#
|
||||
# ubuntu-latest here maps to a real Docker image (act's default is a minimal
|
||||
# image missing tools this repo's workflows expect, e.g. curl for the
|
||||
# Trivy/gitleaks install steps) -- catthehacker's full image matches what
|
||||
# GitHub-hosted (and this fleet's siax-runner-*) ubuntu-latest actually has.
|
||||
-P ubuntu-latest=catthehacker/ubuntu:full-latest
|
||||
-W .gitea/workflows
|
||||
--container-architecture linux/amd64
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BACKLOG P6 (ARK-14): import-boundary check. Advisory today (run manually /
|
||||
* `pnpm run deps:check`) -- not wired as a blocking CI gate in this pass,
|
||||
* so a first real run surfacing findings doesn't turn a green PR red
|
||||
* unexpectedly. Wiring it into CI is a reasonable next step once the
|
||||
* initial finding set (if any) has been triaged.
|
||||
*
|
||||
* Boundaries encoded, matching CLAUDE.md / docs/architecture/ARCHITECTURE.md:
|
||||
* - `services/scrapers` and `services/api` are Python -- not traversed by
|
||||
* this JS/TS dependency graph at all; no rule needed, they're simply
|
||||
* out of dependency-cruiser's file-type scope.
|
||||
* - `src/` (the capability layer, blocking-typechecked) must not import
|
||||
* from `app/` or `components/` (the v0-generated UI layer, advisory-only)
|
||||
* -- that would pull UI-layer assumptions into the tested backend layer.
|
||||
* - Nothing may import from `tests/` (test helpers stay test-only).
|
||||
*/
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
{
|
||||
name: 'no-src-importing-app-or-components',
|
||||
comment:
|
||||
'src/ is the blocking-typechecked capability layer (tsconfig.check.json); app/ and components/ are the advisory v0-generated UI layer. src/ importing from them would pull unverified UI-layer code into the tested backend layer.',
|
||||
severity: 'error',
|
||||
from: { path: '^src' },
|
||||
to: { path: '^(app|components)' },
|
||||
},
|
||||
{
|
||||
name: 'no-importing-tests',
|
||||
comment: 'Test helpers stay test-only -- application code must not depend on tests/.',
|
||||
severity: 'error',
|
||||
from: { pathNot: '^tests' },
|
||||
to: { path: '^tests' },
|
||||
},
|
||||
{
|
||||
name: 'no-circular',
|
||||
comment: 'Circular imports make the module graph hard to reason about and can hide load-order bugs.',
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: { circular: true },
|
||||
},
|
||||
],
|
||||
options: {
|
||||
tsPreCompilationDeps: true,
|
||||
tsConfig: { fileName: 'tsconfig.json' },
|
||||
exclude: { path: 'node_modules|\\.next|services/(api|scrapers)|services/infra' },
|
||||
reporterOptions: {
|
||||
text: { highlightFocused: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "0pt",
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
|
||||
"postCreateCommand": "corepack enable && corepack prepare pnpm@9 --activate && pnpm install --frozen-lockfile",
|
||||
"postStartCommand": "node scripts/siax-doctor.mjs || true",
|
||||
"forwardPorts": [3000],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "editorconfig.editorconfig"]
|
||||
}
|
||||
},
|
||||
"remoteEnv": {
|
||||
"AUTH_ENFORCE": "false"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# BACKLOG P6 (REPO-13). Matches .prettierrc.json's 2-space indent -- this is
|
||||
# a floor for editors that don't run Prettier on save, not a replacement for
|
||||
# it (quote style/semicolons/line width stay Prettier's job, not
|
||||
# EditorConfig's). Note: `pnpm run format` currently reports ~300
|
||||
# pre-existing style violations across this repo (verified 2026-08-11,
|
||||
# out of this pass's scope) -- this file governs new edits, not a
|
||||
# retroactive reformat.
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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]
|
||||
|
||||
- fix(security): security headers (CSP/HSTS/X-Frame-Options/etc.) + CI
|
||||
security gates actually enforce (Trivy `--exit-code 1`, gitleaks) instead
|
||||
of advisory-only. Actions pinned to commit SHA. SBOM + license inventory
|
||||
as CI artifacts. (BACKLOG P0-13, P0-16, P0-19, P1-14, P1-15)
|
||||
- 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)
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,37 @@
|
||||
# BACKLOG P6 / DX-11. Thin wrappers around the real pnpm scripts -- this
|
||||
# file is a convenience entry point, not a second source of truth for what
|
||||
# each step does (see package.json for the actual commands).
|
||||
.PHONY: dev doctor typecheck test lint build install db-up db-down
|
||||
|
||||
install:
|
||||
corepack enable
|
||||
corepack prepare pnpm@9 --activate
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
dev: install
|
||||
pnpm run dev
|
||||
|
||||
# Optional local Postgres (compose.dev.yaml, BACKLOG DX-02) -- `make dev`
|
||||
# above works without this via the in-memory fallback (lib/db.ts). Run this
|
||||
# first, then `DATABASE_URL=postgres://opt:opt-dev-only-not-a-secret@localhost:5432/opt make dev`
|
||||
# for real persistence locally.
|
||||
db-up:
|
||||
docker compose -f compose.dev.yaml up -d
|
||||
|
||||
db-down:
|
||||
docker compose -f compose.dev.yaml down
|
||||
|
||||
doctor:
|
||||
pnpm run siax:doctor
|
||||
|
||||
typecheck:
|
||||
pnpm run typecheck
|
||||
|
||||
test:
|
||||
pnpm run test
|
||||
|
||||
lint:
|
||||
pnpm run lint
|
||||
|
||||
build:
|
||||
pnpm run build
|
||||
@@ -0,0 +1,65 @@
|
||||
# Onboarding — 0pt
|
||||
|
||||
BACKLOG P6 (DX-12). For a human or an agent picking this repo up cold.
|
||||
|
||||
## 1. Read, in order
|
||||
|
||||
1. `CLAUDE.md` — the contract for how this project is built. Non-negotiable
|
||||
constraints (legal sourcing only, Postgres-backbone, provenance-per-field).
|
||||
2. `AGENTS.md` — agent-specific ground rules.
|
||||
3. `docs/architecture/ARCHITECTURE.md` — what the code actually does today
|
||||
(as opposed to `docs/01-11`, which describe target state — see that
|
||||
doc's own "Läsanvisning").
|
||||
4. `README.md` "Known issues" — the live gaps, dated and evidenced.
|
||||
5. `prompts/000-INDEX.md` — run `001-repo-health.md` before touching anything.
|
||||
|
||||
## 2. Get it running
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
corepack prepare pnpm@9 --activate
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run dev # http://localhost:3000, in-memory fallback, no DB needed
|
||||
```
|
||||
|
||||
Or `make dev` (same thing). Want real Postgres locally instead of the
|
||||
in-memory fallback? `make db-up` first (BACKLOG DX-02, `compose.dev.yaml` —
|
||||
**unverified in CI, smoke-test before relying on it**), then re-run with
|
||||
`DATABASE_URL` pointed at it (see the Makefile comment).
|
||||
|
||||
`pnpm run siax:doctor` (or `make doctor`) — runs the self-diagnostic subset
|
||||
of the SIAX Golden Standard v2.0 checks locally (`scripts/siax-doctor.mjs`,
|
||||
control AGENT-04). Run it after cloning and after any structural change.
|
||||
|
||||
## 3. Before you change anything
|
||||
|
||||
- `AUTH_ENFORCE` is fail-closed by default — don't set `AUTH_ENFORCE=false`
|
||||
outside local dev, and never commit it set that way anywhere.
|
||||
- `services/scrapers/*`, `services/infra/docker-compose.data-stack.yml`, and
|
||||
`live/src/workers/adaptive_router.py`'s `curl_cffi` entries are frozen —
|
||||
see `SECURITY.md`. Don't touch, remove, or comment on that code without an
|
||||
explicit, current instruction from the account owner.
|
||||
- Run `prompts/003-security-review.md`'s checklist before pushing anything
|
||||
touching auth, payments, or the platform clients.
|
||||
|
||||
## 4. Before you push
|
||||
|
||||
```sh
|
||||
pnpm run typecheck # blocking in CI
|
||||
pnpm run test # blocking in CI
|
||||
pnpm run build # blocking in CI
|
||||
pnpm run lint # advisory, but fix what you can
|
||||
```
|
||||
|
||||
CI (`.gitea/workflows/ci.yml`) re-runs all of these plus a blocking
|
||||
Trivy/gitleaks scan (`security.yml`, BACKLOG P0-16/P0-19) — a red `trivy`
|
||||
job on an otherwise-unrelated PR usually means a pre-existing dependency
|
||||
finding, not something your change caused; check the run's date against
|
||||
when that job started being blocking before assuming you broke it.
|
||||
|
||||
## 5. Who to ask
|
||||
|
||||
`CODEOWNERS` → `@sax3l` (Simon Axelsson). Two remotes exist for this repo
|
||||
(`git.siax.io/sax3l/0pt` and `github.com/sax3l/0pt`) with independently
|
||||
diverged history — which is authoritative is explicitly Simon's call, not
|
||||
something to assume; see `CLAUDE.md`.
|
||||
@@ -0,0 +1,32 @@
|
||||
# BACKLOG P1-19 (DX-02): local-only Postgres for developers who want real
|
||||
# persistence instead of the in-memory fallback (lib/db.ts / lib/orders-store.ts
|
||||
# already degrade gracefully without this -- `pnpm dev` / `make dev` work
|
||||
# without it, this is additive). Modeled on the already-proven Postgres
|
||||
# service in infra/docker-compose.yml (same image, same sql/ init mount) --
|
||||
# not a copy of the full VPS stack (no NocoDB/Metabase/Caddy/worker here,
|
||||
# those are production concerns, not local dev needs).
|
||||
#
|
||||
# UNVERIFIED (2026-08-11): written and reviewed, but not smoke-tested end to
|
||||
# end in this pass -- no Docker daemon was available in the environment this
|
||||
# was authored in. Verify with `docker compose -f compose.dev.yaml up` before
|
||||
# relying on it.
|
||||
services:
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
environment:
|
||||
POSTGRES_USER: opt
|
||||
POSTGRES_PASSWORD: opt-dev-only-not-a-secret
|
||||
POSTGRES_DB: opt
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- opt-dev-pgdata:/var/lib/postgresql/data
|
||||
- ./sql:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U opt -d opt"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
opt-dev-pgdata: {}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Kubernetes manifests — reference only, NOT the deploy path
|
||||
|
||||
BACKLOG P6 (CD-23). **0pt's actual deploy target is Coolify** (ADR-0001,
|
||||
`siax.config.json`), which runs the committed `Dockerfile` directly — it
|
||||
does not consume Kubernetes manifests. `deploy/k8s/deployment.yaml` in this
|
||||
directory is a reference example only, for the scenario where a future SIAX
|
||||
Cloud node runs k8s instead of/alongside Coolify. It is not applied by any
|
||||
CI/CD path in this repo, and nothing in `.gitea/workflows/` references it.
|
||||
|
||||
Do not `kubectl apply` this against a real cluster without first replacing
|
||||
the placeholder image reference and reviewing resource limits against actual
|
||||
measured usage (none exists yet — 0pt has no live traffic history to size
|
||||
against, see BACKLOG P0-2/D5).
|
||||
@@ -0,0 +1,63 @@
|
||||
# Reference only -- see deploy/k8s/README.md. Not applied by any CI/CD path.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: 0pt-web
|
||||
labels:
|
||||
app: 0pt-web
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: 0pt-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: 0pt-web
|
||||
spec:
|
||||
containers:
|
||||
- name: 0pt-web
|
||||
image: REPLACE_ME/0pt-web:REPLACE_ME_TAG
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
# Real values come from Infisical (BACKLOG P0-1), never from a
|
||||
# committed manifest -- see siax.config.json's `env` list for the
|
||||
# names this app reads.
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: 0pt-web-env
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 3000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/ready
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
# Placeholder -- no live traffic history exists to size against
|
||||
# (BACKLOG P0-2/D5). Replace before any real use.
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "512Mi"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: 0pt-web
|
||||
spec:
|
||||
selector:
|
||||
app: 0pt-web
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
+12
-2
@@ -162,11 +162,21 @@ Flagged so it is not repeated: `src/services/quality-check-service.ts`, `src/ser
|
||||
| Trigger.dev | the existing queue already schedules jobs; Resend covers mail |
|
||||
| FlareSolverr / stealth browsers / undetected-* / proxy-fingerprint rotation / fake-account creation | **prohibited** — see CLAUDE.md §1. Replace with compliant fetching. (The Gitea-only scraper stack described under "Two diverged git remotes" above is exactly this — removed there, never present on GitHub `main`.) |
|
||||
|
||||
## Hosting (cheapest coherent)
|
||||
## Hosting (cheapest coherent) — **SUPERSEDED** (BACKLOG P2-5)
|
||||
|
||||
> **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
|
||||
> **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);
|
||||
> this section is kept for history, not as the current target. Do not read
|
||||
> "Vercel (Hobby/free)" below as still-current guidance.
|
||||
|
||||
- **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.
|
||||
- **Cloudflare R2** — object storage (env-guarded, presign not yet wired — see Components above).
|
||||
- **Postgres** — on the VPS, or **Neon** free tier if you want it managed.
|
||||
|
||||
Full config, env, security, CI/CD: `docs/08-infrastructure.md`. Env var inventory: `.env.example` exists on branch/PR [`feature/0pt-env-example`](https://github.com/sax3l/0pt/pull/3) (unmerged as of this pass) — until it merges to `main`, `docs/08-infrastructure.md` is the source of truth for env var names.
|
||||
Full config, env, security, CI/CD: `docs/08-infrastructure.md`. Env var inventory: [`.env.example`](../.env.example) (present in this repo today — the linked GitHub PR that originally tracked it has long since merged; removed the stale cross-repo link per BACKLOG P3-5).
|
||||
|
||||
@@ -137,12 +137,13 @@ dash.0pt.app { reverse_proxy metabase:3000 }
|
||||
|
||||
## Environment variables
|
||||
|
||||
**`.env.example` now exists** — added on branch `feature/0pt-env-example`
|
||||
(`github.com/sax3l/0pt/pull/3`, **not yet merged to `main`** as of this pass). It was previously
|
||||
impossible to commit: `.gitignore`'s blanket `.env*` rule silently matched `.env.example` too, so it
|
||||
was never added despite `docs/09-build-plan.md` Phase 0 requiring it since day one. The same PR adds
|
||||
`!.env.example` to `.gitignore` to fix that. README's "Env inventory: `.env.example`" link is
|
||||
therefore currently a broken/future reference on `main` until that PR merges.
|
||||
**`.env.example` now exists and is merged to `main`** (BACKLOG P3-5: the stale
|
||||
link to the unmerged `github.com/sax3l/0pt/pull/3` that used to sit here has
|
||||
been removed — that PR landed a while ago). It was previously impossible to
|
||||
commit: `.gitignore`'s blanket `.env*` rule silently matched `.env.example`
|
||||
too, so it was never added despite `docs/09-build-plan.md` Phase 0 requiring
|
||||
it since day one; `.gitignore` now has `!.env.example` to fix that. README's
|
||||
"Env inventory: `.env.example`" link is a real, current reference.
|
||||
|
||||
The table below is grep-verified against `main` this pass, not transcribed from `.env.example`'s own
|
||||
comments — in one case (`ST0RE_URL`, see note) that turned up a discrepancy worth flagging.
|
||||
|
||||
@@ -29,10 +29,10 @@ Phased, in dependency order. **Finish a phase to its acceptance criteria before
|
||||
**No `sql/002_*` or `sql/003_*` exists anywhere in this repo's history** — some other docs
|
||||
(`docs/ICEBERG_WRITE_BOUNDARY.md` etc.) cite a `sql/002_p0_dataset_quality.sql` that was never built;
|
||||
see `docs/08-infrastructure.md`'s "Schema state" section.
|
||||
- `.env.example` — **now exists**, on branch `feature/0pt-env-example`
|
||||
(`github.com/sax3l/0pt/pull/3`), **not yet merged to `main`**. It was blocked for the life of this
|
||||
project by `.gitignore`'s blanket `.env*` rule silently matching it too; the same PR fixes that
|
||||
with `!.env.example`. Until that PR merges, `main` still has no `.env.example`.
|
||||
- `.env.example` — **now exists and is merged to `main`** (BACKLOG P3-5: the
|
||||
stale link to the unmerged `github.com/sax3l/0pt/pull/3` that used to sit
|
||||
here has been removed — that PR has long since landed, `.env.example` is
|
||||
a real, current, gitignore-exempted file at the repo root today).
|
||||
- `infra/docker-compose.yml` and `infra/Caddyfile` are real, committed files (not sketches) covering
|
||||
exactly the services listed. **This pass found no evidence they've ever actually been run** — no
|
||||
CI, no deploy log, no reachable host checked. "File exists and looks correct" ≠ "verified to run";
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# API deprecation policy — 0pt
|
||||
|
||||
BACKLOG P6 (API-24). There is exactly one prior breaking-ish change in this
|
||||
repo's history to learn from: `services/api/orchestrator/main.py` moved from
|
||||
`src/orchestrator` (docs/09-build-plan.md Phase 0) — internal, pre-contract,
|
||||
no external consumers yet. This policy is written before it's needed for a
|
||||
real consumer, on purpose (BACKLOG's own framing: "cheaper to write the
|
||||
policy than to clean up after breaking someone").
|
||||
|
||||
## What counts as breaking
|
||||
|
||||
- Removing a path, operation, or required request/response field from
|
||||
`contracts/openapi.yaml`.
|
||||
- Narrowing an accepted value (enum shrink, type tightening) on a request field.
|
||||
- Changing a response field's meaning without changing its name/type.
|
||||
- Changing default auth/tenant behavior on an existing route (e.g. what
|
||||
BACKLOG P0-4 fixed — the reverse of a break, but the same class of change).
|
||||
|
||||
Not breaking: adding an optional field, adding a new path, widening an
|
||||
accepted value, adding a new enum member a client can ignore.
|
||||
|
||||
## Process
|
||||
|
||||
1. Mark the field/path `deprecated: true` in `contracts/openapi.yaml` with an
|
||||
`x-siax-deprecated-since` date and a one-line replacement pointer.
|
||||
2. Keep the deprecated shape working for **at least one release cycle**
|
||||
after the replacement ships (see `CHANGELOG.md` once real releases/tags
|
||||
exist — BACKLOG P1-3).
|
||||
3. Announce in `CHANGELOG.md`'s `[Unreleased]` section before merge, not
|
||||
after.
|
||||
4. Only remove once nothing in this repo's own consumers (Protosell/FiveCRM,
|
||||
VehIQ per `CLAUDE.md`) is verified to depend on the old shape — "verified"
|
||||
means checked against those repos, not assumed silent.
|
||||
|
||||
## What this repo does NOT have yet
|
||||
|
||||
No consumers are known to be live against this contract yet
|
||||
(`docs/architecture/ARCHITECTURE.md` §2 "Beroenden nedåt": "Inga sådana
|
||||
anrop finns i det här repot" — unverified downstream). This policy exists
|
||||
so the first real breaking change has a process to follow, not because one
|
||||
has happened.
|
||||
@@ -0,0 +1,26 @@
|
||||
# API docs — 0pt
|
||||
|
||||
BACKLOG P1-20 (DOC-07/08). This directory did not exist before this pass.
|
||||
|
||||
**Source of truth:** [`contracts/openapi.yaml`](../../contracts/openapi.yaml)
|
||||
(OpenAPI 3.1, `0pt Data OS API`). Route handlers in `app/api/*` realize
|
||||
these contracts; `x-siax-status` on each path says whether it's `live` or
|
||||
contract-first (target).
|
||||
|
||||
This directory does **not** duplicate the contract as hand-written prose —
|
||||
that would drift the moment one of them is edited and not the other (exactly
|
||||
the kind of stale-doc problem this whole compliance pass exists to fix).
|
||||
Instead:
|
||||
|
||||
| Question | Where to look |
|
||||
|---|---|
|
||||
| What routes exist, request/response shapes | [`contracts/openapi.yaml`](../../contracts/openapi.yaml) directly, or render it with any OpenAPI viewer |
|
||||
| Which routes are live vs. target-only | `x-siax-status` field per path in the same file |
|
||||
| Risk level + auth requirement per route | `docs/architecture/ARCHITECTURE.md` §3 "API-yta" |
|
||||
| Capability → surface (API/CLI/MCP/UI) coverage | `docs/architecture/ARCHITECTURE.md` §5 "Ytor och paritet" |
|
||||
| The separate FastAPI engine contract (`services/api`) | `docs/04-api.md` |
|
||||
| The edge-native P0 capability contract (`app/api/0pt/*`) | `docs/12-edge-capability-api.md` |
|
||||
|
||||
There are genuinely **two** API surfaces documented separately (edge Next.js
|
||||
routes vs. the FastAPI engine) — see `docs/02-architecture.md` "Two API
|
||||
surfaces" for why, before assuming a route lives in the file you expected.
|
||||
@@ -1,11 +1,11 @@
|
||||
# 0PT — Arkitektur
|
||||
|
||||
**Kategori:** platform (K1) **Underkategori:** data-os **Status:** under utveckling (deployad frontend i drift, capability-lagret ej produktionsklart)
|
||||
**Senast verifierad mot kod:** 2026-08-07 (commit `6531384`, main) · **Klassificeringsuppdatering:** 2026-08-10 (`siax.repo.json`)
|
||||
**Senast verifierad mot kod:** 2026-08-07 (commit `6531384`, main) · **Klassificeringsuppdatering:** 2026-08-10 (`siax.repo.json`) · **v2.0-standardmigrering (§5 tillagd, §6–§9 omnumrerade; §6 Deploy-fältet nedan uppdaterat mot `lib/health-checks.ts`):** 2026-08-11 — scoped till dessa avsnitt, ingen fullständig radrad-omverifiering av hela dokumentet gjord denna pass
|
||||
|
||||
> **Läsanvisning.** Detta dokument beskriver vad koden i repot **gör**, inte vad
|
||||
> `README.md`, `CLAUDE.md` eller `docs/01–11` säger att den borde göra. Där de
|
||||
> avviker står avvikelsen utskriven — se §7. Påståenden märkta *(live-verifierat
|
||||
> avviker står avvikelsen utskriven — se §8. Påståenden märkta *(live-verifierat
|
||||
> 2026-08-07)* är kontrollerade mot den körande instansen på `https://0pt.app`.
|
||||
> Allt annat är läst ur källkoden. Där något inte gick att belägga står **OKÄNT**.
|
||||
|
||||
@@ -29,7 +29,7 @@ proveniens (källa, färskhet, konfidens) per fält.
|
||||
| consumes | CL0UD · ST0RE · N0D · AUD0 · INF0 |
|
||||
| replacesSaaS | Apollo · Cognism · ZoomInfo · Clearbit (målet för Data OS: ersätta paid enrichment-SaaS) |
|
||||
| decoupled | `false` |
|
||||
| designSystem | `null` (eget mono/editorial-system, konsumerar inte @siax/ui — öppen fråga §8.8) |
|
||||
| designSystem | `null` (eget mono/editorial-system, konsumerar inte @siax/ui — öppen fråga §9.8) |
|
||||
|
||||
Ersätter krav på betald enrichment-SaaS (Apollo/Cognism/ZoomInfo/Clearbit) med
|
||||
egen compliant sourcing + provenance — målet är att Datahub/VehIQ/data-avdelningar
|
||||
@@ -63,7 +63,7 @@ Tor-rotation, `cloudscraper`, `curl_cffi` TLS-fingerprintbyte, gratis proxypool,
|
||||
nodriver ("undetected browser") och "fake identity generation".
|
||||
`services/infra/docker-compose.data-stack.yml` startar `flaresolverr` och
|
||||
`tor-proxy` som tjänster. Commit `4a164c5` (Simon Axelsson, 2026-07-13) heter
|
||||
ordagrant *"refactor: remove compliance restrictions"*. Se §7 lucka **S9**.
|
||||
ordagrant *"refactor: remove compliance restrictions"*. Se §8 lucka **S9**.
|
||||
|
||||
---
|
||||
|
||||
@@ -74,8 +74,8 @@ ordagrant *"refactor: remove compliance restrictions"*. Se §7 lucka **S9**.
|
||||
| Beroende | Klient | Env-var | Beteende när env saknas |
|
||||
|---|---|---|---|
|
||||
| Zitadel (IdP) | `src/auth/zitadel.ts` | `ZITADEL_ISSUER` (default `https://id-customers.siax.io`) | Använder default-issuern; JWKS hämtas via OIDC-discovery, fallback `<issuer>/oauth/v2/keys` |
|
||||
| CL0UD (policy) | `src/clients/cl0ud-client.ts` | `CL0UD_URL` | **Fail-open** — returnerar alltid `allow` utan nätanrop (§7 S5) |
|
||||
| AUD0 (audit) | `src/clients/aud0-client.ts` | `AUD0_URL` | **No-op** — inga auditevent skrivs (§7 S6) |
|
||||
| CL0UD (policy) | `src/clients/cl0ud-client.ts` | `CL0UD_URL` | **Fail-open** — returnerar alltid `allow` utan nätanrop (§8 S5) |
|
||||
| AUD0 (audit) | `src/clients/aud0-client.ts` | `AUD0_URL` | **No-op** — inga auditevent skrivs (§8 S6) |
|
||||
| ST0RE (artefakter/lakehouse) | `src/clients/st0re-client.ts`, `st0re-lakehouse-client.ts` | `ST0RE_URL` | Returnerar deterministisk mockartefakt |
|
||||
| N0D (jobb) | `src/clients/n0d-client.ts` | `N0D_URL` | Returnerar deterministiskt mockjobb |
|
||||
| Egen data-engine | `lib/engine.ts` | `ENGINE_URL`, `INTERNAL_TOKEN` | `engineFetch` returnerar `null`, edge räknar lokalt |
|
||||
@@ -86,7 +86,7 @@ ordagrant *"refactor: remove compliance restrictions"*. Se §7 lucka **S9**.
|
||||
| Odoo | `src/adapters/odoo-bills-adapter.ts` | `ODOO_BASE_URL` | Adapter inaktiv |
|
||||
|
||||
Mönstret är genomgående: **saknad env ⇒ tyst degradering, inte fel.** Det är
|
||||
bekvämt lokalt och farligt i produktion (§7 S5, S6).
|
||||
bekvämt lokalt och farligt i produktion (§8 S5, S6).
|
||||
|
||||
### Beroenden nedåt (vem konsumerar 0pt)
|
||||
|
||||
@@ -98,7 +98,7 @@ här. Betrakta konsumentlistan som odokumenterad tills motparten visats.
|
||||
|
||||
- **BigQuery** — hård estate-regel. Ej överträdd.
|
||||
- **Hogia** — skrivning förbjuden. Ingen referens finns.
|
||||
- **Protosell-tenantdata** — tenantisolering, se §7 S2.
|
||||
- **Protosell-tenantdata** — tenantisolering, se §8 S2.
|
||||
- Nya SaaS-beroenden utan dokumenterat skäl (`CLAUDE.md` §4, "one tool per job").
|
||||
|
||||
---
|
||||
@@ -116,10 +116,10 @@ Risknivå = SIAX L0–L5-taxonomin, bedömd av mig utifrån vad handlern gör.
|
||||
| Registrera dataset↔Iceberg-mappning | `POST /api/0pt/lakehouse/mappings` | L3 | Nej i kod; `tenant_id` läses ur body |
|
||||
| Lista mappningar | `GET /api/0pt/lakehouse/mappings` | L2 | Nej; `tenant_id` ur query |
|
||||
| Hämta mappning | `GET /api/0pt/lakehouse/mappings/{mappingId}` | L2 | Nej |
|
||||
| Utför Iceberg-write | `POST /api/0pt/lakehouse/mappings/{mappingId}/writes` | **L4** | Nej i kod (CL0UD fail-open, §7 S5) |
|
||||
| Utför Iceberg-write | `POST /api/0pt/lakehouse/mappings/{mappingId}/writes` | **L4** | Nej i kod (CL0UD fail-open, §8 S5) |
|
||||
| Registrera rate-policy | `POST /api/0pt/sources/rate-policies` | L2 | Nej |
|
||||
| Lista/hämta rate-policy | `GET /api/0pt/sources/rate-policies[/{id}]` | L1 | Nej |
|
||||
| Kör källfråga | `POST /api/0pt/sources/{sourceId}/query` | **L4** | Nej; `connection_config` tas ur body (§7 S10) |
|
||||
| Kör källfråga | `POST /api/0pt/sources/{sourceId}/query` | **L4** | Nej; `connection_config` tas ur body (§8 S10) |
|
||||
| Ta emot dokument | `POST /api/0pt/documents/capture` | L3 (PII) | Nej |
|
||||
| Lista dokument | `GET /api/0pt/documents/capture` | L3 (PII) | Nej |
|
||||
| Extrahera dokument | `POST /api/0pt/documents/{documentId}/extraction` | L3 (PII) | Nej |
|
||||
@@ -141,11 +141,11 @@ Risknivå = SIAX L0–L5-taxonomin, bedömd av mig utifrån vad handlern gör.
|
||||
| Läs/ändra order | `GET,PATCH /api/orders/{id}` | L3 | Nej |
|
||||
| **Adminmetrik (orderpipeline, belopp)** | `GET /api/admin/metrics` | **L3** | **Nej** *(live-verifierat: 200 anonymt)* |
|
||||
| Deposition (Stripe checkout) | `POST /api/payments/{order_id}/deposit` | **L4** | Nej |
|
||||
| **Frigör escrow-slutbetalning** | `POST /api/payments/{order_id}/release` | **L4** | **Nej** — sätter dessutom status `delivered` (§7 S4) |
|
||||
| **Frigör escrow-slutbetalning** | `POST /api/payments/{order_id}/release` | **L4** | **Nej** — sätter dessutom status `delivered` (§8 S4) |
|
||||
| Stripe-webhook | `POST /api/webhooks/stripe` | L3 | Signaturkontroll via `STRIPE_WEBHOOK_SECRET` |
|
||||
| Prenumerationer | `GET,POST /api/subscriptions` | L2 | Nej |
|
||||
| Filuppladdning | `GET,POST /api/uploads` | L3 | Nej (`501` utan R2) |
|
||||
| Ta emot kund-DB-grant | `POST /api/connections/db-grant` | **L4** | Nej (`501` utan `GRANT_ENC_KEY`; §7 S7) |
|
||||
| Ta emot kund-DB-grant | `POST /api/connections/db-grant` | **L4** | Nej (`501` utan `GRANT_ENC_KEY`; §8 S7) |
|
||||
| Dev-login | `POST /api/auth/login` | L2 | Ja — kräver `AUTH_SECRET` + matchande `DEV_LOGIN_KEY` |
|
||||
| Logout / me | `POST /api/auth/logout`, `GET /api/auth/me` | L1 | Cookiebaserat |
|
||||
|
||||
@@ -156,7 +156,7 @@ Risknivå = SIAX L0–L5-taxonomin, bedömd av mig utifrån vad handlern gör.
|
||||
`PATCH /orders/{oid}`, `POST /orders/{oid}/run`, `GET /orders/{oid}/status`.
|
||||
**Ingen autentisering alls** — grep efter `Depends`, `auth`, `token`,
|
||||
`X-Internal-Token` i filen ger noll träffar, trots att `lib/engine.ts` skickar
|
||||
`X-Internal-Token`. Se §7 S8.
|
||||
`X-Internal-Token`. Se §8 S8.
|
||||
|
||||
---
|
||||
|
||||
@@ -208,7 +208,7 @@ Två konsekvenser, båda dokumenterade i koden själv:
|
||||
|
||||
- `dim_contact`: namn, roll, e-post, telefon, LinkedIn — persondata under GDPR.
|
||||
- `dim_ubo`: verklig huvudman — persondata, potentiellt känslig.
|
||||
- `db_grants.conn_encrypted`: kunds DB-anslutningssträng (se §7 S7).
|
||||
- `db_grants.conn_encrypted`: kunds DB-anslutningssträng (se §8 S7).
|
||||
- Dokumentcapture (`app/api/0pt/documents/capture`) tar emot godtyckliga
|
||||
dokument, inkl. fakturor/mailbilagor via Graph-adaptern — persondata.
|
||||
|
||||
@@ -220,7 +220,50 @@ inget sådant jobb, ingen TTL, ingen `DELETE`-väg och ingen radering finns i ko
|
||||
|
||||
---
|
||||
|
||||
## 5. Deploy
|
||||
## 5. Ytor och paritet
|
||||
|
||||
**Migrering till standard v2.0 (BACKLOG P1-20/DOC-02):** denna sektion är ny —
|
||||
v1.0-mallen hade 8 rubriker, v2.0 har 9 (Deploy/Drift/Säkerhet/Öppna frågor
|
||||
numrerade om §6–§9 nedan, tidigare §5–§8).
|
||||
|
||||
Källa: `cl0ud packages/capability-registry/src/registry.ts` (samma verifiering
|
||||
som `docs/02-architecture.md`s "Six-surface capability model"-avsnitt, ej
|
||||
omgjord här). 14 `0pt.*`-kapabiliteter är registrerade. Registret har fält för
|
||||
`api`/`cli`/`mcp`/`ui`/`job`; "CL0UD-konsol" nedan är en separat fråga (om
|
||||
kapabiliteten är inbäddad i den delade cl0ud-konsolen) som INTE går att
|
||||
verifiera från detta repo — markerad OKÄNT genomgående, inte gissad.
|
||||
|
||||
"Egen konsol" är **ANTAGANDE baserat på sidnamn/routestruktur under `app/`**,
|
||||
inte spårat anrop-för-anrop mot varje handler — verifiera innan den läses som
|
||||
hårt bevisad.
|
||||
|
||||
| Kapabilitet | API | CLI | MCP | Egen konsol | CL0UD-konsol |
|
||||
|---|---|---|---|---|---|
|
||||
| `0pt.order_create` | ✅ | ❌ | ❌ | ✅ (`app/bestall/*`) | OKÄNT |
|
||||
| `0pt.order_payment` | ✅ | ❌ | ❌ | ✅ (`app/bestall/forskott`) | OKÄNT |
|
||||
| `0pt.estimate_compute` | ✅ | ❌ | ❌ | ✅ (`app/bestall/beskriv`, ANTAGANDE) | OKÄNT |
|
||||
| `0pt.catalog_list` | ✅ | ❌ | ❌ | ✅ (`app/katalog`) | OKÄNT |
|
||||
| `0pt.source_registry_list` | ✅ | ❌ | ❌ | ✅ (`app/kallor`, `app/admin/sources`) | OKÄNT |
|
||||
| `0pt.query_source` | ✅ | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.vehicle_lookup` | ✅ | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.screen_sanctions` | ✅ | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.document_ocr` | ✅ | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.lakehouse_mapping_manage` | ✅ | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.source_rate_policy_manage` | ✅ | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.lakehouse_write` | ✅ (+job) | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.write_iceberg_table` | ✅ (+job) | ❌ | ❌ | ❌ (ingen dedikerad sida funnen) | OKÄNT |
|
||||
| `0pt.write_external_record` | ❌ (job-only, `apiEnabled: false`) | ❌ | ❌ | ❌ | OKÄNT |
|
||||
|
||||
**Luckor, sammanfattat:** CLI = 0/14, MCP = 0/14 (matchar BACKLOG P1-5/P1-6:
|
||||
ingen CLI-yta, ingen MCP-yta alls finns i detta repo ännu). API = 13/14 (allt
|
||||
utom det renodlade job-kapabiliteten). Egen konsol ≈ 5/14, koncentrerat till
|
||||
order-/katalog-/källflödet; kapabiliteter under `app/api/0pt/*` (lakehouse,
|
||||
rate-policy, källfråga, sanktionsscreening, dokument-OCR) har ingen synlig UI
|
||||
alls — matchar BACKLOG P1-8 ("`/api/0pt/*`-kapabiliteterna saknar UI").
|
||||
|
||||
---
|
||||
|
||||
## 6. Deploy
|
||||
|
||||
| Fält | Verifierat läge |
|
||||
|---|---|
|
||||
@@ -233,11 +276,11 @@ inget sådant jobb, ingen TTL, ingen `DELETE`-väg och ingen radering finns i ko
|
||||
| **CI** | `.gitea/workflows/ci.yml`. `quality`-jobb: typecheck (blockerande, endast `src/` + `tests/`), vitest (blockerande, 17 testfiler), eslint (rådgivande), `next build` (blockerande). `security`-jobb: Trivy `--exit-code 0` (rådgivande). Kör på `ubuntu-latest`. |
|
||||
|
||||
> **Konflikt att fatta beslut om:** estate-projektet *GCP/Vercel-exit* säger att
|
||||
> flottan ska bort från Vercel. 0pt:s enda körande instans ligger på Vercel. Se §8.
|
||||
> flottan ska bort från Vercel. 0pt:s enda körande instans ligger på Vercel. Se §9.
|
||||
|
||||
---
|
||||
|
||||
## 6. Drift
|
||||
## 7. Drift
|
||||
|
||||
### Hälsokontroll
|
||||
`GET /api/health` → `{ok, service:"0pt-web", db, queue, ts}`. Den svarar **alltid
|
||||
@@ -277,7 +320,7 @@ Om/när Postgres provisioneras måste detta lösas separat — jfr estate-blocke
|
||||
|
||||
---
|
||||
|
||||
## 7. Säkerhet
|
||||
## 8. Säkerhet
|
||||
|
||||
### Vad som faktiskt finns
|
||||
|
||||
@@ -396,7 +439,7 @@ bindning till en användarpost. Kommentaren erkänner att det är ett scaffold;
|
||||
|
||||
---
|
||||
|
||||
## 8. Öppna frågor
|
||||
## 9. Öppna frågor
|
||||
|
||||
1. **Varför kör inte middlewaren i produktion (S1)?** Måste rotorsaksanalyseras.
|
||||
Ingen dokumentation får påstå att `/api/0pt/*` är skyddat innan en anonym
|
||||
@@ -408,7 +451,7 @@ bindning till en användarpost. Kommentaren erkänner att det är ett scaffold;
|
||||
3. **Vercel eller flottan?** *(config-delen löst 2026-08-09, trafik-delen kvarstår.)*
|
||||
0pt:s enda **körande** instans ligger fortfarande på Vercel — den ändringen är
|
||||
medvetet INTE gjord här (skulle kräva en egen cutover-plan: verifiera att inget
|
||||
riktigt kunddata/trafik ligger bakom `0pt.app` — vilket är fallet idag, se §5/§6:
|
||||
riktigt kunddata/trafik ligger bakom `0pt.app` — vilket är fallet idag, se §6/§7:
|
||||
`/api/health` visar `db/queue: not_configured`, ordrar lever bara in-memory —
|
||||
provisionera en Coolify-app, peka DNS om, och avveckla Vercel-projektet, i den
|
||||
ordningen). Det som ÄR gjort: `siax.config.json` deklarerar nu `target: "coolify"`
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# ADR-0001: Deploy target is Coolify (SIAX Cloud fleet), not Vercel
|
||||
|
||||
- **Status:** Accepted (config declares it; migration not yet executed)
|
||||
- **Date decided:** 2026-08-09 (commit `ad042d8`, "fix(deploy): declare Coolify as the deploy target, not Vercel")
|
||||
- **Date of this record:** 2026-08-11 (BACKLOG P1-2 — this ADR documents an
|
||||
already-made decision retroactively; it does not introduce a new one)
|
||||
|
||||
## Context
|
||||
|
||||
0pt was originally scaffolded and deployed on Vercel (Hobby/free tier,
|
||||
project `0pt`, scope *Simon Axelsson's projects*). SIAX Cloud's platform
|
||||
mandate is to replace paid SaaS with self-hosted fleet infrastructure
|
||||
(Hetzner + Coolify), matching every other SIAX Cloud OS repo (`n0d`,
|
||||
`st0re`, `aud0`, ...).
|
||||
|
||||
## Decision
|
||||
|
||||
`siax.config.json`'s `deploy.target` is `"coolify"`, not `"vercel"`. A
|
||||
Dockerfile written for Coolify (multi-stage, non-root `node` user, `HEALTHCHECK`
|
||||
against `/api/health`) and `.gitea/workflows/cd.yml` (POSTs a restart to a
|
||||
Coolify app UUID on push to `main`) both exist and are committed.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **This is a config-and-tooling change, not a traffic cutover.** As of this
|
||||
record, no Coolify application has been provisioned for this repo —
|
||||
`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.
|
||||
- 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
|
||||
the standard's "safe to run now" classification).
|
||||
- Until the cutover happens, docs describing Vercel as *where 0pt actually
|
||||
runs today* are accurate; docs prescribing Vercel as *the plan going
|
||||
forward* are stale (see BACKLOG P2-5 — `docs/02-architecture.md`'s
|
||||
"Hosting" section is marked SUPERSEDED for exactly this reason).
|
||||
|
||||
## See also
|
||||
|
||||
- `docs/architecture/ARCHITECTURE.md` §6 "Deploy", §9 "Öppna frågor"
|
||||
- `README.md` "Deploy" section (kept current with the live/target split)
|
||||
- `docs/compliance/BACKLOG.md` P0-2, P1-17, P3-1
|
||||
@@ -0,0 +1,57 @@
|
||||
# ADR-0002: `/api/0pt/*` authenticates via fleet Zitadel Bearer tokens, not NextAuth/Auth.js
|
||||
|
||||
- **Status:** Accepted and implemented
|
||||
- **Date decided:** ~2026-07 ("Wave 2" — commit `1d76d50`,
|
||||
"feat(auth,foundation): Wave 2 flag-gated Zitadel JWT verify + validation
|
||||
+ eslint + tests"), fail-closed default landed commit `1c1e2cb`
|
||||
("fix(auth): make the AUTH_ENFORCE default fail-closed on `/api/0pt/*`")
|
||||
- **Date of this record:** 2026-08-11 (BACKLOG P1-2 — retroactive)
|
||||
|
||||
## Context
|
||||
|
||||
0pt's capability surface (`/api/0pt/*`) needs to authenticate requests
|
||||
against a real identity, and needs that identity to carry a SIAX tenant so
|
||||
`x-0pt-verified-tenant` can be trusted downstream. SIAX Cloud already runs a
|
||||
fleet-wide Zitadel IdP (`id-customers.siax.io`) for exactly this purpose —
|
||||
building a second, repo-local identity provider would duplicate it and
|
||||
create a second source of truth for "who is this".
|
||||
|
||||
## Decision
|
||||
|
||||
`middleware.ts` verifies inbound `Authorization: Bearer` tokens against the
|
||||
fleet Zitadel IdP (`src/auth/zitadel.ts`): JWKS discovered from
|
||||
`<issuer>/.well-known/openid-configuration` (falling back to
|
||||
`<issuer>/oauth/v2/keys`), tenant derived from a documented claim-key
|
||||
precedence (`urn:zitadel:iam:org:id` → `urn:zitadel:iam:user:resourceowner:id`
|
||||
→ `tenant_id` → `tenant`). No NextAuth/Auth.js dependency was added.
|
||||
|
||||
Enforcement is fail-closed by default: `AUTH_ENFORCE` unset (or any value
|
||||
except the literal string `"false"`) requires a valid, verified token — 401
|
||||
on missing/invalid, 403 when a client-supplied `X-Tenant-Id` disagrees with
|
||||
the token's own tenant claim. `AUTH_ENFORCE=false` is a local-dev-only escape
|
||||
hatch, and is refused at boot when `NODE_ENV=production`
|
||||
(`assertAuthConfigSafe()` throws rather than serving anonymously).
|
||||
|
||||
A separate, older HMAC session cookie (`lib/auth.ts`, with a `DEV_LOGIN_KEY`
|
||||
dev-login shortcut) exists in parallel and is explicitly **not** the auth
|
||||
system this ADR is about — it predates the Zitadel work, has no MFA/CSRF/
|
||||
revocation, and is tracked as its own gap (BACKLOG P0-14, gated on the
|
||||
account owner's decision about what replaces it).
|
||||
|
||||
## Consequences
|
||||
|
||||
- 0pt does not maintain its own user/credential store for the capability
|
||||
layer — identity is entirely delegated to the fleet Zitadel instance.
|
||||
- A live-verified gap was found and is tracked separately: a 2026-08-10
|
||||
probe found `GET 0pt.app/api/0pt` returning 200 anonymously despite the
|
||||
code being fail-closed (BACKLOG P0-4/P0-5, root cause unresolved as of
|
||||
this record — do not assume the deployed instance enforces this until an
|
||||
anonymous request gets a real 401 there).
|
||||
- `middleware.ts` sits outside `tsconfig.check.json`'s blocking type-check
|
||||
scope (BACKLOG P0-5) — a real gap, not addressed by this ADR.
|
||||
|
||||
## See also
|
||||
|
||||
- `docs/architecture/ARCHITECTURE.md` §8 "Säkerhet"
|
||||
- `tests/auth/{context,zitadel}.test.ts`
|
||||
- `docs/compliance/BACKLOG.md` P0-4, P0-5, P0-14
|
||||
@@ -0,0 +1,47 @@
|
||||
# ADR-0003: Lakehouse table format is Apache Iceberg (pointer to `n0d` ADR-0030)
|
||||
|
||||
- **Status:** Accepted (decision owned by `n0d`, not by this repo)
|
||||
- **Date decided:** documented in the `n0d` repo as ADR-0030; referenced in
|
||||
this repo since commit `2545218` ("Document lakehouse write-path ownership
|
||||
decision (ADR-0030)")
|
||||
- **Date of this record:** 2026-08-11 (BACKLOG P1-2 — this is a **pointer**
|
||||
ADR, not a duplicate decision; it exists so `docs/architecture/decisions/`
|
||||
is a complete index of decisions that shape this repo, even the ones made
|
||||
elsewhere)
|
||||
|
||||
## Context
|
||||
|
||||
BACKLOG P1-2 asks for ADRs to exist in this repo. The lakehouse-format
|
||||
decision itself was made once, ecosystem-wide, in `n0d` (the repo that owns
|
||||
the platform's write path across all K1 data producers) — re-deciding it
|
||||
here would create a second, divergent source of truth for a cross-repo
|
||||
architectural choice.
|
||||
|
||||
## Decision
|
||||
|
||||
Apache Iceberg is the canonical SIAX Cloud lakehouse table format. 0pt is a
|
||||
**write-path owner** within that decision: it maps its own datasets to
|
||||
Iceberg tables and requests writes through the documented boundary (N0D
|
||||
executes, 0pt requests — see `docs/ICEBERG_WRITE_BOUNDARY.md`). This repo
|
||||
does not re-litigate the Iceberg-vs-alternatives choice; it implements its
|
||||
own slice of it.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `docs/10-lakehouse-decision.md` and `docs/11-lakehouse-write-plan.md` are
|
||||
this repo's own follow-up documentation against the `n0d`-owned decision —
|
||||
read them alongside this pointer, not instead of it.
|
||||
- `docs/ICEBERG_WRITE_BOUNDARY.md` records the implementation status,
|
||||
including which parts are live-verified vs. contract-only. That document,
|
||||
not this ADR, is the source of truth for "is the write path actually
|
||||
working" — this ADR only records *which format was chosen and by whom*.
|
||||
- Any future change to the ecosystem-wide format decision would originate in
|
||||
`n0d`, not here; this repo would need its own follow-up ADR to record how
|
||||
it adapts.
|
||||
|
||||
## See also
|
||||
|
||||
- `docs/10-lakehouse-decision.md`
|
||||
- `docs/11-lakehouse-write-plan.md`
|
||||
- `docs/ICEBERG_WRITE_BOUNDARY.md`
|
||||
- `n0d` repo, ADR-0030 (external — not vendored into this repo)
|
||||
@@ -0,0 +1,34 @@
|
||||
# Architecture Decision Records — 0pt
|
||||
|
||||
BACKLOG P1-2. One file per decision, numbered sequentially, never renumbered
|
||||
or deleted (superseded decisions get a new ADR that says so, per the
|
||||
convention below).
|
||||
|
||||
| ADR | Title | Status |
|
||||
|---|---|---|
|
||||
| [0001](0001-deploy-target-coolify-not-vercel.md) | Deploy target is Coolify, not Vercel | Accepted (migration not yet executed) |
|
||||
| [0002](0002-auth-zitadel-bearer-not-nextauth.md) | `/api/0pt/*` auth is fleet Zitadel Bearer, not NextAuth | Accepted and implemented |
|
||||
| [0003](0003-lakehouse-format-iceberg-pointer.md) | Lakehouse format is Apache Iceberg (pointer to `n0d` ADR-0030) | Accepted (owned by `n0d`) |
|
||||
|
||||
## Deliberately not yet written here
|
||||
|
||||
Two decisions the original BACKLOG (P1-2) named explicitly are **not**
|
||||
written as ADRs in this pass, on purpose:
|
||||
|
||||
- **Payments provider (Stripe vs. Revolut vs. Hyperswitch).** Gated on the
|
||||
account owner's decision (BACKLOG P1-23) — writing an ADR that presents a
|
||||
choice as "accepted" before it's actually been decided would misrepresent
|
||||
the repo's own state, which is exactly what this whole documentation pass
|
||||
is trying to stop doing.
|
||||
- **The `services/scrapers/*` keep-vs-remove question.** Explicitly out of
|
||||
scope for this pass — see `SECURITY.md` and the dated, attributed
|
||||
provenance record already maintained for that decision elsewhere in this
|
||||
repo's compliance docs. Not duplicated here.
|
||||
|
||||
## Convention
|
||||
|
||||
New ADR: `NNNN-short-kebab-title.md`, next sequential number, status one of
|
||||
`Proposed` / `Accepted` / `Superseded by ADR-NNNN` / `Rejected`. Record the
|
||||
date the decision was actually made (from `git log`/commit history where
|
||||
possible), not just the date the ADR file was written, when they differ —
|
||||
see 0001–0003 for the pattern.
|
||||
@@ -0,0 +1,150 @@
|
||||
# P6 guides — 0pt
|
||||
|
||||
BACKLOG P6: 19 GUIDE-level items, all marked S (small effort), all "NU"
|
||||
(safe to write now). Several have their own concrete artifact instead of a
|
||||
guide entry here — see the table. This file covers the rest: real,
|
||||
actionable "how to build this when it's prioritized" guidance grounded in
|
||||
0pt's actual code, not generic advice that could apply to any repo.
|
||||
|
||||
| Control | Item | Where |
|
||||
|---|---|---|
|
||||
| REPO-13 | `.editorconfig` | [`.editorconfig`](../../.editorconfig) |
|
||||
| ARK-14 | dependency-cruiser | [`.dependency-cruiser.cjs`](../../.dependency-cruiser.cjs), `pnpm run deps:check` |
|
||||
| API-24 | deprecation policy | [`docs/api/DEPRECATION_POLICY.md`](../api/DEPRECATION_POLICY.md) |
|
||||
| CD-23 | K8s manifest | [`deploy/k8s/`](../../deploy/k8s/) (reference only, not the deploy path) |
|
||||
| CI-16 | `act` | [`.actrc`](../../.actrc) |
|
||||
| DX-11 | Makefile | [`Makefile`](../../Makefile) |
|
||||
| DX-12 | onboarding | [`ONBOARDING.md`](../../ONBOARDING.md) |
|
||||
| CLI-13 | `doctor` CLI subcommand | see §1 below |
|
||||
| DOC-18 | documentation site | see §2 below |
|
||||
| UI-30 | PWA | see §3 below |
|
||||
| IAM-19 | impersonation | see §4 below |
|
||||
| PAY-11 | Hyperswitch ADR | see §5 below |
|
||||
| AI-18 | assistant | see §6 below |
|
||||
| OBS-21 | status page | see §7 below |
|
||||
| TEST-20 | chaos testing | see §8 below |
|
||||
| NET-19 | IPv6 | see §9 below |
|
||||
| SCALE-13 | read replicas | see §10 below |
|
||||
| OSS-10 | upstream contribution | see §11 below |
|
||||
| AGENT-13 | nightly agent | see §12 below |
|
||||
|
||||
---
|
||||
|
||||
## 1. CLI-13 — `doctor` CLI subcommand
|
||||
|
||||
There is no CLI surface at all yet (BACKLOG P1-5 — L effort, not attempted
|
||||
this pass). `pnpm run siax:doctor` / `make doctor`
|
||||
(`scripts/siax-doctor.mjs`) already covers the underlying check logic
|
||||
(AGENT-04). When P1-5 builds `apps/cli`, wire a `siax 0pt doctor` subcommand
|
||||
that shells out to (or re-imports) `scripts/siax-doctor.mjs` rather than
|
||||
re-implementing the checks — one source of truth for "is this repo
|
||||
compliant", not two that can drift.
|
||||
|
||||
## 2. DOC-18 — documentation site
|
||||
|
||||
Not built. When prioritized: `contracts/openapi.yaml` already exists as a
|
||||
real OpenAPI 3.1 SSOT, so the lowest-effort path is a static OpenAPI
|
||||
renderer (e.g. Redocly CLI or Scalar) pointed at that file, deployed as a
|
||||
static site — not a hand-authored docs site that would drift from the
|
||||
contract. `docs/*.md` could be published alongside via any static-site
|
||||
generator that reads plain Markdown (Docusaurus, Starlight); pick whichever
|
||||
matches what other SIAX Cloud repos already use, per BACKLOG's "one tool per
|
||||
job" rule — check `cl0ud`/`n0d` first before introducing a new one here.
|
||||
|
||||
## 3. UI-30 — PWA
|
||||
|
||||
Not built. Would need: a `manifest.json` (icons already exist at
|
||||
`public/icon*.png`/`icon.svg` — reuse them, don't regenerate), a service
|
||||
worker (Next.js has no built-in PWA plugin as of this repo's Next 16; a
|
||||
minimal hand-written SW covering `/api/health`-style offline detection would
|
||||
be more honest than a cargo-culted `next-pwa` config, given this app's
|
||||
`ignoreBuildErrors: true` posture already has enough unverified surface). Low
|
||||
priority — 0pt's UI is transactional (order flow, portal), not the kind of
|
||||
app users install to a home screen.
|
||||
|
||||
## 4. IAM-19 — impersonation
|
||||
|
||||
Not built. Needed pattern once an admin portal exists with real customer
|
||||
data: a support/admin session that can view-as a specific tenant/user,
|
||||
**always** logged to AUD0 (BACKLOG P0-18 — AUD0 client exists but is
|
||||
currently no-op without `AUD0_URL`) with the impersonator's real identity
|
||||
attached, never silently. Do not build a "become this user" auth shortcut
|
||||
without that audit trail landing first — an impersonation feature with no
|
||||
audit log is a privilege-escalation bug wearing a support-tool costume.
|
||||
|
||||
## 5. PAY-11 — Hyperswitch ADR
|
||||
|
||||
**Proposed, not accepted — payments provider is explicitly gated on the
|
||||
account owner's decision (BACKLOG P1-23).** This entry exists so the option
|
||||
is on record, not to decide it. Hyperswitch is an open-source payment
|
||||
orchestration layer (self-hostable) that could sit in front of
|
||||
Stripe/Revolut/others rather than 0pt integrating each provider directly
|
||||
(`lib/stripe.ts` today). Trade-off to weigh when P1-23 is actually decided:
|
||||
self-hosting Hyperswitch adds an operational dependency (another service to
|
||||
run/monitor) in exchange for provider-agnosticism; direct integration
|
||||
(current state) is simpler but couples this repo to Stripe's specific API
|
||||
shape. Not evaluated further here — that's the ADR this guide is explicitly
|
||||
not writing.
|
||||
|
||||
## 6. AI-18 — assistant
|
||||
|
||||
Not built. `app/api/ai/parse` exists today as a heuristic (no model call,
|
||||
per `CLAUDE.md`/`docs/02-architecture.md`'s "honest heuristic" framing) — a
|
||||
real assistant feature should route through INF0 (the fleet AI gateway,
|
||||
`INF0_URL` — documented target, not yet read by any code in this repo) per
|
||||
the platform mandate, never a raw provider key committed here.
|
||||
|
||||
## 7. OBS-21 — status page
|
||||
|
||||
Not built. Lowest-effort honest option once `/api/health` + `/api/ready`
|
||||
are actually monitored (BACKLOG P1-11, not yet wired to Prometheus/GlitchTip
|
||||
per that item): a self-hosted status page (e.g. Upptime, which is
|
||||
just-a-GitHub/Gitea-Actions-cron + static site, no server to run) polling
|
||||
those two endpoints. Do not stand up a status page before the endpoints it
|
||||
would display are meaningfully monitored — an unmonitored status page that
|
||||
says "operational" is worse than no status page.
|
||||
|
||||
## 8. TEST-20 — chaos testing
|
||||
|
||||
Not built, and low-value until BACKLOG P0-12 lands (real DB/persistence —
|
||||
right now there's no durable state to chaos-test; killing the process loses
|
||||
nothing that wasn't already going to be lost on the next cold start). Once
|
||||
persistence exists: start with the cheapest chaos test that matches 0pt's
|
||||
actual documented failure mode — kill the Postgres connection mid-request
|
||||
and assert `/api/ready` flips to 503 within one health-check interval (this
|
||||
is now directly testable thanks to BACKLOG P0-3's real dependency probes).
|
||||
|
||||
## 9. NET-19 — IPv6
|
||||
|
||||
Not evaluated. `docs/architecture/ARCHITECTURE.md` doesn't currently record
|
||||
whether the Coolify target node(s) or `0pt.app`'s DNS have AAAA records —
|
||||
check `z0ne` (SIAX's own DNS product) once a real Coolify app exists for
|
||||
this repo; premature to configure before there's a fleet IP to point at.
|
||||
|
||||
## 10. SCALE-13 — read replicas
|
||||
|
||||
Not applicable yet — there is no primary database in production to
|
||||
replicate (BACKLOG P0-12: state lives in in-memory `Map`s, zero DB
|
||||
provisioned). Revisit once P0-12 lands and real read/write load is
|
||||
measured; provisioning a replica against a database that doesn't exist yet
|
||||
would be pure speculation.
|
||||
|
||||
## 11. OSS-10 — upstream contribution
|
||||
|
||||
No dependency in this repo has been identified as needing a patch upstream
|
||||
rather than a workaround here (checked: no local patches in
|
||||
`pnpm-lock.yaml`'s `patchedDependencies`, none found). Nothing to
|
||||
contribute upstream today; revisit if/when a real workaround is added for
|
||||
an upstream bug — file the issue/PR there instead of carrying the
|
||||
workaround silently forever.
|
||||
|
||||
## 12. AGENT-13 — nightly agent
|
||||
|
||||
`nightly.yml` already re-runs the full quality + (now blocking) security
|
||||
gate daily (BACKLOG P0-16). A step beyond that — an agent that reads the
|
||||
nightly failure and opens a triage PR — is AGENT-06 territory (BACKLOG P1-19
|
||||
already scopes AGENT-06 as not attempted this pass: it needs real
|
||||
OpenHands/Aider/SWE-agent infrastructure via INF0, which doesn't exist yet
|
||||
for this repo). Do not build a bespoke nightly-agent script here ahead of
|
||||
that shared infrastructure landing — it would be exactly the kind of
|
||||
one-off tool the "one tool per job" rule (`CLAUDE.md` §4) warns against.
|
||||
+4
-1
@@ -14,7 +14,9 @@
|
||||
"test:watch": "vitest",
|
||||
"format": "prettier --check .",
|
||||
"format:write": "prettier --write .",
|
||||
"typecheck:app": "tsc --noEmit -p tsconfig.json"
|
||||
"typecheck:app": "tsc --noEmit -p tsconfig.json",
|
||||
"siax:doctor": "node scripts/siax-doctor.mjs",
|
||||
"deps:check": "depcruise src app components lib --config .dependency-cruiser.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.5",
|
||||
@@ -78,6 +80,7 @@
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"dependency-cruiser": "^18.2.0",
|
||||
"eslint": "^9.15.0",
|
||||
"postcss": "^8.5",
|
||||
"prettier": "^3.9.6",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@siax/0pt-schemas",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "BACKLOG P1-21: makes packages/schemas a real workspace package (it was previously loose JSON Schema files with no package.json, so pnpm-workspace.yaml's `packages/*` glob matched an empty package). Zero build step -- these files are consumed by path, not imported as JS.",
|
||||
"files": ["*.schema.json"],
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
Generated
+250
@@ -186,6 +186,9 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^19
|
||||
version: 19.2.3(@types/react@19.2.17)
|
||||
dependency-cruiser:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
eslint:
|
||||
specifier: ^9.15.0
|
||||
version: 9.39.5(jiti@2.7.0)
|
||||
@@ -211,6 +214,8 @@ importers:
|
||||
specifier: ^3.2.7
|
||||
version: 3.2.7(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
|
||||
|
||||
packages/schemas: {}
|
||||
|
||||
packages:
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
@@ -1681,16 +1686,32 @@ packages:
|
||||
'@vitest/utils@3.2.7':
|
||||
resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==}
|
||||
|
||||
acorn-jsx-walk@2.0.0:
|
||||
resolution: {integrity: sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn-loose@8.5.2:
|
||||
resolution: {integrity: sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@8.17.0:
|
||||
resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
acorn@8.18.0:
|
||||
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
ajv@6.15.0:
|
||||
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
|
||||
|
||||
@@ -1786,6 +1807,10 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
commander@15.0.0:
|
||||
resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -1865,6 +1890,11 @@ packages:
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
dependency-cruiser@18.2.0:
|
||||
resolution: {integrity: sha512-xMDoLD0no6pDInR8/4rIIqZ4mERDnsjezk8PkNORYSfBLvjCOogUxaruepmi1uQtZQlYUgdT2u7G3jTlgKqNjw==}
|
||||
engines: {node: ^22||^24||>=26}
|
||||
hasBin: true
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1895,6 +1925,14 @@ packages:
|
||||
resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
enhanced-resolve@5.24.5:
|
||||
resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
@@ -2012,6 +2050,9 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
get-nonce@1.0.1:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2020,6 +2061,10 @@ packages:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
global-directory@4.0.1:
|
||||
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
globals@14.0.0:
|
||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2031,6 +2076,10 @@ packages:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -2047,6 +2096,10 @@ packages:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
ini@4.1.1:
|
||||
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
input-otp@1.4.1:
|
||||
resolution: {integrity: sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==}
|
||||
peerDependencies:
|
||||
@@ -2057,6 +2110,14 @@ packages:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
interpret@3.1.1:
|
||||
resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
is-core-module@2.16.2:
|
||||
resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2065,6 +2126,14 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-installed-globally@1.0.0:
|
||||
resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
is-path-inside@4.0.0:
|
||||
resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
@@ -2094,9 +2163,18 @@ packages:
|
||||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
|
||||
json5@2.2.3:
|
||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
kleur@3.0.3:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2203,6 +2281,9 @@ packages:
|
||||
minimatch@3.1.5:
|
||||
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -2273,6 +2354,9 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
@@ -2311,6 +2395,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
@@ -2404,15 +2492,31 @@ packages:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
rechoir@0.8.0:
|
||||
resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
||||
regexp-tree@0.1.27:
|
||||
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
|
||||
hasBin: true
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve@1.22.12:
|
||||
resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
rollup@4.62.2:
|
||||
resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
safe-regex@2.1.1:
|
||||
resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==}
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
@@ -2436,6 +2540,9 @@ packages:
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
sonner@1.7.4:
|
||||
resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==}
|
||||
peerDependencies:
|
||||
@@ -2452,6 +2559,10 @@ packages:
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
strip-bom@3.0.0:
|
||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2485,6 +2596,10 @@ packages:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
tailwind-merge@3.6.0:
|
||||
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
|
||||
|
||||
@@ -2531,6 +2646,14 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4'
|
||||
|
||||
tsconfig-paths-webpack-plugin@4.2.0:
|
||||
resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
tsconfig-paths@4.2.0:
|
||||
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
@@ -2672,6 +2795,11 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
watskeburt@6.0.0:
|
||||
resolution: {integrity: sha512-jfiuDABaxSkC71T6oZ3vCS99roYkSHm/+As+G0Dz8taAHQb+SJBvLEm5RlsgG71XdfAj3rv7eudUBTgwcQUPlQ==}
|
||||
engines: {node: ^22.13||^24||>=26}
|
||||
hasBin: true
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4033,12 +4161,28 @@ snapshots:
|
||||
loupe: 3.2.1
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
acorn-jsx-walk@2.0.0: {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.17.0):
|
||||
dependencies:
|
||||
acorn: 8.17.0
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.18.0):
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
|
||||
acorn-loose@8.5.2:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
|
||||
acorn@8.17.0: {}
|
||||
|
||||
acorn@8.18.0: {}
|
||||
|
||||
ajv@6.15.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -4137,6 +4281,8 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
commander@15.0.0: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -4199,6 +4345,27 @@ snapshots:
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
dependency-cruiser@18.2.0:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
acorn-jsx: 5.3.2(acorn@8.18.0)
|
||||
acorn-jsx-walk: 2.0.0
|
||||
acorn-loose: 8.5.2
|
||||
acorn-walk: 8.3.5
|
||||
commander: 15.0.0
|
||||
enhanced-resolve: 5.24.5
|
||||
ignore: 7.0.6
|
||||
interpret: 3.1.1
|
||||
is-installed-globally: 1.0.0
|
||||
json5: 2.2.3
|
||||
picomatch: 4.0.5
|
||||
prompts: 2.4.2
|
||||
rechoir: 0.8.0
|
||||
safe-regex: 2.1.1
|
||||
semver: 7.8.5
|
||||
tsconfig-paths-webpack-plugin: 4.2.0
|
||||
watskeburt: 6.0.0
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
@@ -4227,6 +4394,13 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.3
|
||||
|
||||
enhanced-resolve@5.24.5:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.3
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
esbuild@0.28.1:
|
||||
@@ -4373,18 +4547,28 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
glob-parent@6.0.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
global-directory@4.0.1:
|
||||
dependencies:
|
||||
ini: 4.1.1
|
||||
|
||||
globals@14.0.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.6: {}
|
||||
@@ -4396,6 +4580,8 @@ snapshots:
|
||||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
ini@4.1.1: {}
|
||||
|
||||
input-otp@1.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
@@ -4403,12 +4589,25 @@ snapshots:
|
||||
|
||||
internmap@2.0.3: {}
|
||||
|
||||
interpret@3.1.1: {}
|
||||
|
||||
is-core-module@2.16.2:
|
||||
dependencies:
|
||||
hasown: 2.0.4
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-installed-globally@1.0.0:
|
||||
dependencies:
|
||||
global-directory: 4.0.1
|
||||
is-path-inside: 4.0.0
|
||||
|
||||
is-path-inside@4.0.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
jiti@2.7.0: {}
|
||||
@@ -4429,10 +4628,14 @@ snapshots:
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@@ -4517,6 +4720,8 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.16
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.15: {}
|
||||
@@ -4580,6 +4785,8 @@ snapshots:
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pathval@2.0.1: {}
|
||||
@@ -4608,6 +4815,11 @@ snapshots:
|
||||
|
||||
prettier@3.9.6: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
sisteransi: 1.0.5
|
||||
|
||||
prop-types@15.8.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@@ -4704,8 +4916,21 @@ snapshots:
|
||||
tiny-invariant: 1.3.3
|
||||
victory-vendor: 36.9.2
|
||||
|
||||
rechoir@0.8.0:
|
||||
dependencies:
|
||||
resolve: 1.22.12
|
||||
|
||||
regexp-tree@0.1.27: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve@1.22.12:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
is-core-module: 2.16.2
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
rollup@4.62.2:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
@@ -4737,6 +4962,10 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.62.2
|
||||
fsevents: 2.3.3
|
||||
|
||||
safe-regex@2.1.1:
|
||||
dependencies:
|
||||
regexp-tree: 0.1.27
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
semver@7.8.5: {}
|
||||
@@ -4781,6 +5010,8 @@ snapshots:
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
sonner@1.7.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
@@ -4792,6 +5023,8 @@ snapshots:
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
strip-bom@3.0.0: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strip-literal@3.1.0:
|
||||
@@ -4811,6 +5044,8 @@ snapshots:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
tailwind-merge@3.6.0: {}
|
||||
|
||||
tailwindcss-animate@1.0.7(tailwindcss@4.3.1):
|
||||
@@ -4842,6 +5077,19 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
tsconfig-paths-webpack-plugin@4.2.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
enhanced-resolve: 5.24.5
|
||||
tapable: 2.3.3
|
||||
tsconfig-paths: 4.2.0
|
||||
|
||||
tsconfig-paths@4.2.0:
|
||||
dependencies:
|
||||
json5: 2.2.3
|
||||
minimist: 1.2.8
|
||||
strip-bom: 3.0.0
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tw-animate-css@1.3.3: {}
|
||||
@@ -4996,6 +5244,8 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
watskeburt@6.0.0: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
# Prompts — index
|
||||
|
||||
**Kontroll-ID:** AGENT-02 (denna katalog + index), AGENT-03 (varje enskild
|
||||
prompt nedan har mål/kontext/förutsättningar/omfattning/kontrakt/
|
||||
acceptanskriterier/kontroll-ID/förbjudna åtgärder)
|
||||
|
||||
> Operativa prompt-mallar för agentsessioner mot 0pt. Konvention: `NNN-roll.md`.
|
||||
> Alla mallar respekterar CLAUDE.md/AGENTS.md + docs/compliance (AR-5).
|
||||
> Självdiagnos: `pnpm siax:doctor` (AGENT-04, `scripts/siax-doctor.mjs`)
|
||||
> kör den maskinellt mätbara delmängden av dessa krav lokalt.
|
||||
|
||||
| Fil | Användning |
|
||||
|---|---|
|
||||
|
||||
@@ -1,22 +1,65 @@
|
||||
# Prompt: Repo-hälsa (0pt)
|
||||
|
||||
**Kontroll-ID:** AGENT-03 (denna mall), AGENT-02 (`prompts/`-strukturen som helhet)
|
||||
|
||||
> Använd före annat arbete: verifiera att utgångsläget är sant innan ändringar.
|
||||
|
||||
## Mål
|
||||
|
||||
Fastställa repots verkliga läge (branch, CI, live `/api/health`) innan någon
|
||||
kod ändras, så att arbetet bygger på fakta, inte på vad ett dokument påstår.
|
||||
|
||||
## Kontext
|
||||
|
||||
`main` på detta repo har varit mål för flera parallella sessioner (se
|
||||
`docs/compliance/BACKLOG.md`s löpande historik). Antaganden om "senaste
|
||||
läget" blir fel snabbt — kör alltid denna prompt först.
|
||||
|
||||
## Förutsättningar
|
||||
|
||||
- Läs-åtkomst till `git.siax.io/sax3l/0pt` (klon eller `git fetch`).
|
||||
- Nätåtkomst till `https://0pt.app` för live-proben (annars: notera EJ
|
||||
VERIFIERAT i output, gissa aldrig).
|
||||
|
||||
## Omfattning
|
||||
|
||||
Läsning + rapportering endast. Denna prompt gör inga kodändringar.
|
||||
|
||||
## 1. Status
|
||||
|
||||
- `git status` + `git log --oneline -5` — ren main?
|
||||
- Senaste CI-run (Gitea Actions API):
|
||||
`/repos/sax3l/0pt/actions/runs?limit=5` — ci.yml ska vara grön;
|
||||
cd.yml/deploy.yml röd = avsiktlig guard (placeholder UUID).
|
||||
- `curl -s https://0pt.app/api/health` — ok/true + db/queue-läge.
|
||||
`/repos/sax3l/0pt/actions/runs?limit=5` — `ci.yml` ska vara grön;
|
||||
`cd.yml` röd = avsiktlig guard (placeholder UUID).
|
||||
- `curl -s https://0pt.app/api/health` — `ok`/beroendestatus (se BACKLOG P0-3:
|
||||
fältet rapporterar numera riktig per-dependency-status, inte bara
|
||||
`DATABASE_URL`/`REDIS_URL`-närvaro).
|
||||
|
||||
## 2. Luckor att minnas
|
||||
|
||||
- S1: `/api/0pt/*` svarade 200 anonymt (2026-08-10) — påstå aldrig "skyddad".
|
||||
- S2: tenant tas ur body — nya handlers läser `x-0pt-verified-tenant`.
|
||||
- Bypass-stacken `services/scrapers/*` — medvetet behållen, rör ej.
|
||||
- S1: `/api/0pt/*` svarade 200 anonymt (2026-08-10) — påstå aldrig "skyddad"
|
||||
utan en färsk 401-verifiering.
|
||||
- S2: tenant tas ur body i äldre handlers — nya handlers läser
|
||||
`x-0pt-verified-tenant`.
|
||||
- Bypass-stacken `services/scrapers/*` — medvetet behållen, rör ej (se
|
||||
`SECURITY.md`).
|
||||
|
||||
## Kontrakt / maskinellt verifierbara acceptanskriterier
|
||||
|
||||
- [ ] `git log -1 --format=%H` rapporterad i output.
|
||||
- [ ] CI-status för senaste `ci.yml`-run på `main` rapporterad (grön/röd + run-ID).
|
||||
- [ ] `/api/health`-svarets `ok`-fält + per-dependency-status citerat ordagrant
|
||||
(inte omskrivet) med tidsstämpel för när proben kördes.
|
||||
- [ ] Status på S1–S3 märkt VERIFIERAT/ANTAGANDE/EJ VERIFIERAT — aldrig
|
||||
olabellat.
|
||||
|
||||
## 3. Output
|
||||
|
||||
Rapportera: HEAD-SHA, CI-status, /api/health-svar, status på S1–S3
|
||||
Rapportera: HEAD-SHA, CI-status, `/api/health`-svar, status på S1–S3
|
||||
(VERIFIERAT/ANTAGANDE/EJ VERIFIERAT).
|
||||
|
||||
## Förbjudna åtgärder
|
||||
|
||||
- Skriv aldrig till `main` från denna prompt.
|
||||
- Påstå aldrig ett kontrollresultat utan att ha kört den bakomliggande
|
||||
kommandot/proben denna session.
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
# Prompt: Compliance-audit (0pt)
|
||||
|
||||
**Kontroll-ID:** AGENT-03 (denna mall), AGENT-04/AGENT-05 (denna prompt är den
|
||||
manuella/agent-drivna motsvarigheten till `pnpm siax:doctor` + framtida
|
||||
C0NF0RM-rapportering — se `scripts/siax-doctor.mjs`)
|
||||
|
||||
> Uppföljning av GOLDEN-STANDARD-REPORT. Körs när backlog-objekt stängts.
|
||||
|
||||
## Mål
|
||||
|
||||
Hålla `docs/compliance/GOLDEN-STANDARD-REPORT.md` och `docs/compliance/BACKLOG.md`
|
||||
i synk med repots faktiska läge — aldrig med önskeläget.
|
||||
|
||||
## Kontext
|
||||
|
||||
Poängen i GOLDEN-STANDARD-REPORT.md är avsedd att jämföras mot andra SIAX
|
||||
Cloud-repon. En falskt hög poäng (ändrad utan nytt bevis) gör den jämförelsen
|
||||
värdelös för hela flottan, inte bara för detta repo.
|
||||
|
||||
## Förutsättningar
|
||||
|
||||
- `docs/compliance/GOLDEN-STANDARD-REPORT.md` och `docs/compliance/BACKLOG.md`
|
||||
existerar och lästa i sin helhet innan ändring.
|
||||
- Det påstådda beviset (testfil, commit-SHA, live-svar) finns och går att peka på.
|
||||
|
||||
## Omfattning
|
||||
|
||||
Endast `docs/compliance/**`. Kör aldrig kodändringar från denna prompt — de
|
||||
hör hemma i den ändring som faktiskt stänger backlog-posten.
|
||||
|
||||
## 1. Uppdatera
|
||||
|
||||
- Läs `docs/compliance/GOLDEN-STANDARD-REPORT.md` — sätt status ✅/⚠️/❌/➖
|
||||
@@ -17,3 +43,17 @@
|
||||
attribuerade beslut (Simon Axelsson, 2026-08-11, se SECURITY.md), INTE det
|
||||
gamla oattribuerade "medvetet användarbeslut" — ingen ändring av C6-status.
|
||||
- Inga fabricerade förbättringar: om inget faktiskt ändrats → ingen poängändring.
|
||||
|
||||
## Kontrakt / maskinellt verifierbara acceptanskriterier
|
||||
|
||||
- [ ] Varje ✅/⚠️→bättre statusändring har en bevisrad (filväg/SHA/kommando)
|
||||
i samma commit.
|
||||
- [ ] Poängräkningen i "## Poäng" summerar korrekt om man räknar om den
|
||||
(✅×1 + ⚠️×0.5) / (antal kontroller − antal ➖).
|
||||
- [ ] C6 oförändrad ➖ om inte en ny, daterad, attribuerad ägarinstruktion citeras.
|
||||
|
||||
## Förbjudna åtgärder
|
||||
|
||||
- Ändra aldrig C6:s status eller motivering utan en ny, explicit,
|
||||
daterad instruktion från kontoägaren i denna session.
|
||||
- Höj aldrig en kontrolls poäng utan ett konkret, citerat bevis.
|
||||
|
||||
@@ -1,19 +1,63 @@
|
||||
# Prompt: Security review (0pt)
|
||||
|
||||
**Kontroll-ID:** AGENT-03 (denna mall)
|
||||
|
||||
> Säkerhetsgranskning av en ändring före push. Kör ALLTID vid ändringar i
|
||||
> auth, klienter (cl0ud/aud0/n0d/st0re), payments eller middleware.
|
||||
|
||||
## Mål
|
||||
|
||||
Fånga regressions i auth/tenant/hemlighetshantering innan de når `main`,
|
||||
komplement till (inte ersättning för) CI:s blockerande Trivy/gitleaks-steg
|
||||
(`.gitea/workflows/security.yml`).
|
||||
|
||||
## Kontext
|
||||
|
||||
Två av repots historiskt allvarligaste fynd (S1 anonym `/api/0pt/*`, S3
|
||||
anonym `/api/orders`) hade upptäckts av denna checklista om den körts före
|
||||
push. CI:s automatiska scanning (BACKLOG P0-16/P0-19) täcker beroenden och
|
||||
hemligheter i historiken — den täcker inte logikfel som "tenant_id läst ur
|
||||
body".
|
||||
|
||||
## Förutsättningar
|
||||
|
||||
- Diffen som ska granskas är känd (branch/PR-nummer).
|
||||
- `pnpm run typecheck` och `pnpm run test` går att köra lokalt.
|
||||
|
||||
## Omfattning
|
||||
|
||||
Granskning av EN ändring/PR. Ändrar inget i sig själv — output är en rapport,
|
||||
inte en fix.
|
||||
|
||||
## 1. Checklista
|
||||
|
||||
- [ ] Inga hemligheter i diffen (grep: sk_/whsec/private key/AIza/AKIA…).
|
||||
- [ ] Inga hemligheter i diffen (grep: sk_/whsec/private key/AIza/AKIA…) —
|
||||
CI:s gitleaks-steg är facit, men kör lokalt först.
|
||||
- [ ] Inga nya betald-SaaS-/Google/Vercel-bärare (AR-4).
|
||||
- [ ] `tenant_id` inte tagen ur request-body (S2-regeln).
|
||||
- [ ] Inga ändringar i `services/scrapers/*` (bypass-stacken, medvetet beslut).
|
||||
- [ ] Auth-fail-closed kvarstår; ingen `AUTH_ENFORCE=false`-produktionsväg.
|
||||
- [ ] Inga `console.log` av hemligheter; ingen `NEXT_PUBLIC_*`-hemlighet.
|
||||
- [ ] Tester täcker nya grenar; typecheck+test gröna.
|
||||
- [ ] Trivy-fynd i diffen triagerade och dokumenterade.
|
||||
- [ ] Trivy-fynd i diffen triagerade och dokumenterade (BACKLOG P0-16: Trivy
|
||||
är numera blockerande i CI — ett rött `trivy`-jobb är förväntat tills
|
||||
befintliga beroende-fynd åtgärdas separat, inte en regression du orsakat
|
||||
om det redan var rött på `main`).
|
||||
|
||||
## Kontrakt / maskinellt verifierbara acceptanskriterier
|
||||
|
||||
- [ ] Varje checklisterad punkt har VERIFIERAT/ANTAGANDE/EJ VERIFIERAT +
|
||||
risknivå (L0–L5) i output — ingen punkt lämnas tom.
|
||||
- [ ] Om `services/scrapers/*` finns i diffen: granskningen stoppar och
|
||||
eskalerar, gör ingen egen bedömning.
|
||||
|
||||
## 2. Output
|
||||
|
||||
`VERIFIERAT/ANTAGANDE/EJ VERIFIERAT` per punkt + risknivå (L0–L5) + rekommendation.
|
||||
|
||||
## Förbjudna åtgärder
|
||||
|
||||
- Godkänn aldrig en diff som rör `services/scrapers/*`,
|
||||
`services/infra/docker-compose.data-stack.yml` eller
|
||||
`live/src/workers/adaptive_router.py`s `curl_cffi`-rader — eskalera i stället.
|
||||
- Fatta aldrig auktoritativt beslut om C6/bypass-stacken från denna prompt.
|
||||
|
||||
@@ -1,8 +1,35 @@
|
||||
# Prompt: Deploy-cutover (0pt)
|
||||
|
||||
**Kontroll-ID:** AGENT-03 (denna mall)
|
||||
|
||||
> Mål: 0pt.app på Coolify, sedan Vercel-avveckling. Följ
|
||||
> `docs/runbook/deploy.md` steg för steg.
|
||||
|
||||
## Mål
|
||||
|
||||
Flytta produktionstrafik från Vercel till den provisionerade Coolify-appen
|
||||
utan dataförlust eller nedtid, och utan att avveckla Vercel förrän paritet
|
||||
är bevisad.
|
||||
|
||||
## Kontext
|
||||
|
||||
`siax.config.json` deklarerar redan `deploy.target: "coolify"` (ADR-0001)
|
||||
— detta är avsiktligt bara en konfigurationsavsikt, inte en genomförd
|
||||
flytt. Ingen Coolify-app finns provisionerad för detta repo än
|
||||
(`.gitea/workflows/cd.yml`s `COOLIFY_APP_ID` är en medveten platshållare).
|
||||
|
||||
## Förutsättningar
|
||||
|
||||
- Coolify-åtkomst (cloud.siax.io) för att skapa appen.
|
||||
- Infisical-projekt för 0pt (BACKLOG P0-1 — **kräver en Infisical-token som
|
||||
denna prompts session inte nödvändigtvis har**; verifiera innan steg 1).
|
||||
- `COOLIFY_TOKEN`-repo-secret-behörighet.
|
||||
|
||||
## Omfattning
|
||||
|
||||
Infra-provisionering + DNS + en skyddad-sökväg-PR (`cd.yml`). Rör aldrig
|
||||
`services/scrapers/*` eller andra frysta sökvägar under denna cutover.
|
||||
|
||||
## 1. Gate (blockerare)
|
||||
|
||||
- Coolify-app existerar? (cloud.siax.io → projekt)
|
||||
@@ -15,10 +42,28 @@
|
||||
1. Provisionera app (dockerfile, repo sax3l/0pt, branch main).
|
||||
2. PR: fyll UUID + verifiera status-check (skyddade filer).
|
||||
3. Kör deploy, följ Gitea-run → grön.
|
||||
4. `curl https://0pt.app/api/health` och en marknadssida.
|
||||
4. `curl https://0pt.app/api/health` **och** `curl https://0pt.app/api/ready`
|
||||
(BACKLOG P0-3 — readiness failar stängt om databasen inte är uppe; kräv
|
||||
200 på båda, inte bara `/api/health`) och en marknadssida.
|
||||
5. DNS-ompekning + Vercel-avveckling först när paritet certifierats.
|
||||
|
||||
## 3. Regel
|
||||
|
||||
Radera aldrig Vercel-projektet före certifierad paritet. Dokumentera varje steg
|
||||
i `docs/compliance/EVIDENCE/` med datum + SHA.
|
||||
|
||||
## Kontrakt / maskinellt verifierbara acceptanskriterier
|
||||
|
||||
- [ ] `/api/health` OCH `/api/ready` svarar 200 på den nya Coolify-URL:en
|
||||
innan DNS pekas om.
|
||||
- [ ] Varje steg i sekvensen har en motsvarande rad i
|
||||
`docs/compliance/EVIDENCE/` med datum + SHA innan nästa steg påbörjas.
|
||||
- [ ] Vercel-projektet finns kvar (overifierat raderat) tills steg 5 är klart.
|
||||
|
||||
## Förbjudna åtgärder
|
||||
|
||||
- Radera eller pausa Vercel-projektet före certifierad paritet.
|
||||
- Fyll `COOLIFY_APP_ID` eller `COOLIFY_TOKEN` med gissade/platshållarvärden
|
||||
som INTE är den verkliga provisionerade appens uppgifter.
|
||||
- Rör `services/scrapers/*`, `services/infra/docker-compose.data-stack.yml`
|
||||
under denna cutover — helt orelaterat till deploy-målet.
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
// siax-doctor (BACKLOG P1-19, control AGENT-04): "Repot är
|
||||
// self-diagnostiserande: `pnpm siax:doctor` kör hela standarden lokalt och
|
||||
// skriver ut avvikelser med kontroll-ID."
|
||||
//
|
||||
// This is a repo-local, zero-dependency subset -- NOT the published
|
||||
// @siax/doctor package from sax3l/siax-standard. That package was
|
||||
// deliberately not taken on as a dependency here: at the time this was
|
||||
// written it wasn't verified reachable/stable from this repo's frozen
|
||||
// lockfile install, and a doctor script that can silently fail to install
|
||||
// is worse than no doctor script. This checks the same class of
|
||||
// machine-checkable facts (required files, manifest shape) using only
|
||||
// Node's built-in `fs`/`path` -- no network, no registry.
|
||||
//
|
||||
// Exit code 0 = every check passed. Exit code 1 = at least one check failed
|
||||
// (see the printed control-ID to find the BACKLOG/standard item).
|
||||
//
|
||||
// NOTE for reviewers: this script was authored alongside two sibling PRs
|
||||
// (fix/honest-health-check adds app/api/ready/route.ts;
|
||||
// ci/security-headers-and-scanning adds CODEOWNERS) that this branch does
|
||||
// not itself contain. Run this on a checkout with all three merged for a
|
||||
// clean 0-failure result -- a couple of FAILs on this branch alone, for
|
||||
// exactly those two files, is expected and not a bug in either PR.
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
/** @type {{id: string, label: string, check: () => boolean | string}[]} */
|
||||
const checks = [];
|
||||
|
||||
function file(path) {
|
||||
return join(ROOT, path);
|
||||
}
|
||||
|
||||
function requireFile(id, label, path) {
|
||||
checks.push({
|
||||
id,
|
||||
label,
|
||||
check: () => (existsSync(file(path)) ? true : `missing: ${path}`),
|
||||
});
|
||||
}
|
||||
|
||||
function requireJsonField(id, label, path, fieldPath) {
|
||||
checks.push({
|
||||
id,
|
||||
label,
|
||||
check: () => {
|
||||
if (!existsSync(file(path))) return `missing file: ${path}`;
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(readFileSync(file(path), "utf8"));
|
||||
} catch (e) {
|
||||
return `invalid JSON in ${path}: ${e.message}`;
|
||||
}
|
||||
let cur = data;
|
||||
for (const key of fieldPath.split(".")) {
|
||||
if (cur == null || !(key in cur)) return `missing field "${fieldPath}" in ${path}`;
|
||||
cur = cur[key];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function requireTextMatch(id, label, path, pattern, patternLabel) {
|
||||
checks.push({
|
||||
id,
|
||||
label,
|
||||
check: () => {
|
||||
if (!existsSync(file(path))) return `missing file: ${path}`;
|
||||
const text = readFileSync(file(path), "utf8");
|
||||
return pattern.test(text) ? true : `${path} does not contain ${patternLabel}`;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// -- REPO-* / DOC-* : required root files --------------------------------
|
||||
requireFile("DOC-01", "README.md exists", "README.md");
|
||||
requireFile("REPO-08", "LICENSE exists", "LICENSE");
|
||||
requireFile("REPO-13", ".editorconfig exists", ".editorconfig");
|
||||
checks.push({
|
||||
id: "REPO-15/P0-19",
|
||||
label: "no committed .env (real secrets)",
|
||||
check: () => (existsSync(file(".env")) ? "a real .env file is committed -- should never happen" : true),
|
||||
});
|
||||
requireFile("P1-15", "CODEOWNERS exists", "CODEOWNERS");
|
||||
requireFile("P1-3", "CHANGELOG.md exists", "CHANGELOG.md");
|
||||
requireFile("SEC-*", "SECURITY.md exists", "SECURITY.md");
|
||||
requireFile("AGENT-01", "AGENTS.md exists", "AGENTS.md");
|
||||
requireFile("DOC-01", "CONTRIBUTING.md exists", "CONTRIBUTING.md");
|
||||
|
||||
// -- DOC-02/DOC-04 : ARCHITECTURE.md at the exact standard path -----------
|
||||
requireFile("DOC-02", "docs/architecture/ARCHITECTURE.md exists at the standard path", "docs/architecture/ARCHITECTURE.md");
|
||||
requireTextMatch(
|
||||
"DOC-02/v2.0",
|
||||
"ARCHITECTURE.md has the v2.0 '## 5. Ytor och paritet' section",
|
||||
"docs/architecture/ARCHITECTURE.md",
|
||||
/^## 5\. Ytor och paritet/m,
|
||||
"'## 5. Ytor och paritet'",
|
||||
);
|
||||
requireTextMatch(
|
||||
"DOC-04",
|
||||
"ARCHITECTURE.md has a 'Senast verifierad mot kod' date field",
|
||||
"docs/architecture/ARCHITECTURE.md",
|
||||
/Senast verifierad mot kod:\*\*\s*\d{4}-\d{2}-\d{2}/,
|
||||
"a dated 'Senast verifierad mot kod' field",
|
||||
);
|
||||
|
||||
// -- P1-1 / REPO-03/04/06 : manifests --------------------------------------
|
||||
requireFile("P1-1/REPO-04", "siax.repo.json exists", "siax.repo.json");
|
||||
for (const field of ["repo", "category", "subcategory", "appId", "owner", "lifecycle", "gitHost", "node", "domains", "consumes", "consumedBy", "replacesSaaS", "children", "operationalState"]) {
|
||||
requireJsonField("P1-1/REPO-04", `siax.repo.json has required field "${field}"`, "siax.repo.json", field);
|
||||
}
|
||||
requireFile("P1-1/REPO-06", "siax.config.json exists", "siax.config.json");
|
||||
for (const field of ["framework", "build", "start", "port", "healthcheck", "environments"]) {
|
||||
requireJsonField("P1-1/REPO-06", `siax.config.json has required field "${field}"`, "siax.config.json", field);
|
||||
}
|
||||
|
||||
// -- AGENT-02/03 : prompts -------------------------------------------------
|
||||
requireFile("AGENT-02", "prompts/000-INDEX.md exists", "prompts/000-INDEX.md");
|
||||
|
||||
// -- DX-03 : devcontainer --------------------------------------------------
|
||||
requireFile("DX-03", ".devcontainer/devcontainer.json exists", ".devcontainer/devcontainer.json");
|
||||
|
||||
// -- P1-2 : ADRs -------------------------------------------------------------
|
||||
requireFile("P1-2", "docs/architecture/decisions/ has at least one ADR", "docs/architecture/decisions/README.md");
|
||||
|
||||
// -- CI-14 : contract ---------------------------------------------------------
|
||||
requireFile("A4/DOC", "contracts/openapi.yaml exists", "contracts/openapi.yaml");
|
||||
|
||||
// -- CD-04/OBS-04 : health/ready ---------------------------------------------
|
||||
requireFile("P0-3/CD-04", "app/api/health/route.ts exists", "app/api/health/route.ts");
|
||||
requireFile("P0-3/OBS-04", "app/api/ready/route.ts exists (readiness, not just liveness)", "app/api/ready/route.ts");
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
let failures = 0;
|
||||
console.log("siax-doctor -- 0pt (repo-local subset, see scripts/siax-doctor.mjs header)\n");
|
||||
for (const { id, label, check } of checks) {
|
||||
const result = check();
|
||||
if (result === true) {
|
||||
console.log(` [PASS] ${id.padEnd(14)} ${label}`);
|
||||
} else {
|
||||
failures += 1;
|
||||
console.log(` [FAIL] ${id.padEnd(14)} ${label} -- ${result}`);
|
||||
}
|
||||
}
|
||||
console.log(`\n${checks.length - failures}/${checks.length} checks passed.`);
|
||||
if (failures > 0) {
|
||||
console.log(`${failures} check(s) failed -- see control-IDs above and cross-reference docs/compliance/BACKLOG.md.`);
|
||||
process.exit(1);
|
||||
}
|
||||
+53
-16
@@ -1,19 +1,56 @@
|
||||
{
|
||||
"name": "0pt",
|
||||
"category": "platform",
|
||||
"subcategory": "data-os",
|
||||
"appId": "0PT",
|
||||
"owner": "SIAX Technology AB",
|
||||
"packageName": "@siax/0pt",
|
||||
"capabilityPrefix": "0pt",
|
||||
"deploy": {
|
||||
"target": "coolify",
|
||||
"node": null,
|
||||
"domain": "0pt.app",
|
||||
"healthPath": "/api/health"
|
||||
"$schema": "https://git.siax.io/sax3l/siax-standard/raw/branch/main/schema/siax.config.schema.json",
|
||||
"framework": "nextjs",
|
||||
"build": "pnpm run build",
|
||||
"start": "pnpm run start",
|
||||
"port": 3000,
|
||||
"healthcheck": {
|
||||
"path": "/api/health",
|
||||
"readyPath": "/api/ready"
|
||||
},
|
||||
"consumes": ["CL0UD", "ST0RE", "N0D", "AUD0", "INF0"],
|
||||
"replacesSaaS": ["Apollo", "Cognism", "ZoomInfo", "Clearbit"],
|
||||
"decoupled": false,
|
||||
"designSystem": null
|
||||
"environments": {
|
||||
"prod": {
|
||||
"domain": "0pt.app"
|
||||
}
|
||||
},
|
||||
"env": [
|
||||
"DATABASE_URL",
|
||||
"POSTGRES_URL",
|
||||
"POSTGRES_PRISMA_URL",
|
||||
"REDIS_URL",
|
||||
"AUTH_SECRET",
|
||||
"AUTH_URL",
|
||||
"DEV_LOGIN_KEY",
|
||||
"INTERNAL_TOKEN",
|
||||
"ZITADEL_ISSUER",
|
||||
"AUTH_ENFORCE",
|
||||
"ZITADEL_TENANT_CLAIM",
|
||||
"ENGINE_URL",
|
||||
"CL0UD_URL",
|
||||
"N0D_URL",
|
||||
"AUD0_URL",
|
||||
"INF0_URL",
|
||||
"ST0RE_URL",
|
||||
"STRIPE_SECRET_KEY",
|
||||
"STRIPE_WEBHOOK_SECRET",
|
||||
"NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
|
||||
"R2_ENDPOINT",
|
||||
"R2_BUCKET",
|
||||
"R2_ACCESS_KEY_ID",
|
||||
"R2_ACCOUNT_ID",
|
||||
"R2_SECRET_ACCESS_KEY",
|
||||
"DOC_INTELLIGENCE_ENDPOINT",
|
||||
"DOC_INTELLIGENCE_KEY",
|
||||
"DOC_INTELLIGENCE_API_VERSION",
|
||||
"GRANT_ENC_KEY",
|
||||
"ODOO_BASE_URL",
|
||||
"RESEND_API_KEY",
|
||||
"METABASE_SITE_URL",
|
||||
"METABASE_SECRET_KEY",
|
||||
"NOCODB_BASE_URL",
|
||||
"NOCODB_API_TOKEN",
|
||||
"NEXT_PUBLIC_APP_URL",
|
||||
"NEXT_PUBLIC_API_URL"
|
||||
],
|
||||
"decoupled": false
|
||||
}
|
||||
|
||||
+47
-22
@@ -1,34 +1,59 @@
|
||||
{
|
||||
"$schema": "https://docs.siax.io/schemas/siax.repo.schema.json",
|
||||
"name": "0pt",
|
||||
"$schema": "https://git.siax.io/sax3l/siax-standard/raw/branch/main/schema/siax.repo.schema.json",
|
||||
"repo": "0pt",
|
||||
"standardVersion": "2.0",
|
||||
"category": "platform",
|
||||
"subcategory": "data-os",
|
||||
"appId": "0PT",
|
||||
"capabilityPrefix": "0pt",
|
||||
"packageName": "@siax/0pt",
|
||||
"owner": "SIAX Technology AB",
|
||||
"codeowners": ["@sax3l"],
|
||||
"lifecycle": "active",
|
||||
"operationalState": "running",
|
||||
"gitHost": "git.siax.io",
|
||||
"node": null,
|
||||
"domains": [
|
||||
"0pt.app",
|
||||
"api.0pt.app"
|
||||
{
|
||||
"fqdn": "0pt.app",
|
||||
"env": "prod",
|
||||
"purpose": "Next.js edge plane -- marketing, catalog, order flow, customer/admin portal, capability API",
|
||||
"registrar": "EJ VERIFIERAT fran detta repo -- se docs/architecture/ARCHITECTURE.md SS6/SS9 (BACKLOG P0-15)"
|
||||
},
|
||||
{
|
||||
"fqdn": "api.0pt.app",
|
||||
"env": "prod",
|
||||
"purpose": "Target API host (Caddy) -- documented target, not verified live (contracts/openapi.yaml server list)",
|
||||
"registrar": "EJ VERIFIERAT fran detta repo -- se docs/architecture/ARCHITECTURE.md SS6/SS9 (BACKLOG P0-15)"
|
||||
}
|
||||
],
|
||||
"deploy": {
|
||||
"target": "coolify",
|
||||
"healthPath": "/api/health"
|
||||
"consumes": ["CL0UD", "ST0RE", "N0D", "AUD0", "INF0"],
|
||||
"consumedBy": [],
|
||||
"children": [
|
||||
{
|
||||
"path": "app",
|
||||
"purpose": "Next.js edge plane -- UI + capability/order/payment API route handlers",
|
||||
"lifecycle": "active"
|
||||
},
|
||||
{
|
||||
"path": "services/api",
|
||||
"purpose": "FastAPI data engine (orchestrator + workers) -- legal sourcing chain, estimates/orders/screening",
|
||||
"lifecycle": "active"
|
||||
},
|
||||
{
|
||||
"path": "services/scrapers",
|
||||
"purpose": "Scraper stack including WAF-bypass code -- kept per a dated, attributed account-owner decision under active review; NOT part of the compliant sourcing chain (see SECURITY.md, CLAUDE.md SS1)",
|
||||
"lifecycle": "active"
|
||||
},
|
||||
{
|
||||
"path": "packages/schemas",
|
||||
"purpose": "Shared JSON Schema definitions for 0pt data contracts (dataset/iceberg/lineage/signal/rate-policy)",
|
||||
"lifecycle": "active"
|
||||
}
|
||||
],
|
||||
"replacesSaaS": ["Apollo", "Cognism", "ZoomInfo", "Clearbit"],
|
||||
"contracts": {
|
||||
"openapi": "contracts/openapi.yaml"
|
||||
},
|
||||
"consumes": [
|
||||
"CL0UD",
|
||||
"ST0RE",
|
||||
"N0D",
|
||||
"AUD0",
|
||||
"INF0"
|
||||
],
|
||||
"replacesSaaS": [
|
||||
"Apollo",
|
||||
"Cognism",
|
||||
"ZoomInfo",
|
||||
"Clearbit"
|
||||
],
|
||||
"decoupled": false,
|
||||
"designSystem": null
|
||||
"decoupled": false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user