Files
0pt/docs/guides/document-workbench.md
T
admin 023406c9b6
CI (SIAX Cloud) / integration (push) Skipped
CI (SIAX Cloud) / sonar (push) Skipped
masterplan-lock / check (push) Successful in 3s
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 5s
Security Scan (SIAX Cloud) / security (push) Successful in 14s
CI (SIAX Cloud) / security (push) Successful in 21s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 19s
CI (SIAX Cloud) / security (pull_request) Successful in 23s
CI (SIAX Cloud) / integration (pull_request) Successful in 28s
CI (SIAX Cloud) / quality (push) Successful in 48s
CI (SIAX Cloud) / quality (pull_request) Successful in 45s
feat(auth): Zitadel OIDC login for the portal
The portal had no way to obtain a verified principal: /api/0pt/* requires a
Bearer token, but the browser only had the dev-only HMAC cookie.

- GET /api/auth/login: Authorization Code + PKCE against Zitadel.
- GET /api/auth/callback: code exchange, id_token verification (issuer,
  audience, nonce) and a signed session cookie with sub + tenant.
- middleware accepts the signed session as a verified principal; Bearer wins.
- dev login POST is refused in production.
- Sign-in link in the portal shell.
2026-11-02 15:48:02 +01:00

4.2 KiB

Document workbench — upload → structure → validated export

The customer-facing slice that turns an uploaded file into evidence-bound data.

Routes

Route Purpose Fails closed with
POST /api/0pt/documents/upload multipart upload of the original bytes into DOC0, recorded in a durable attempt journal before dispatch 503 DOC0_NOT_CONFIGURED, 503 PROJECT_AUTHORIZATION_NOT_CONFIGURED, 503 UPLOAD_JOURNAL_NOT_CONFIGURED
POST /api/0pt/documents/structure re-reads the DOC0 segment index, calls INF0, validates every field against an exact source span, reserves/settles credits 503 WORKBENCH_NOT_CONFIGURED, 503 MODEL_POLICY_NOT_CONFIGURED, 503 CREDITS_NOT_CONFIGURED, 402 INSUFFICIENT_CREDITS
GET /api/0pt/documents/models the server-approved model routes and limits 503 MODEL_POLICY_NOT_CONFIGURED
GET /api/0pt/credits/balance tenant credit balance 503 CREDITS_NOT_CONFIGURED
GET /api/0pt/credits/packs sellable packs from the server-owned price book 503 PRICE_BOOK_NOT_CONFIGURED
POST /api/0pt/credits/checkout Stripe Checkout session + server price snapshot 503 PRICE_BOOK_NOT_CONFIGURED, 503 STRIPE_NOT_CONFIGURED
POST /api/webhooks/stripe re-reads the session and payment intent, then grants credits idempotently non-2xx until processed

UI: /portal/dokument (upload, structure, review, download) and /portal/krediter (balance, packs, checkout). The portal signs in through Zitadel OIDC (/api/auth/login → /api/auth/callback); the resulting signed session cookie is accepted by middleware as a verified principal (Bearer still wins).

Invariants

  1. Tenant and actor come only from middleware's verified headers. A customer route never trusts tenant_id/actor_id from the body.
  2. Project access is checked by OpenFGA (@siax/access), the same primitive DOC0 uses. Until the central relationship store is deployed, an explicit server-owned grant list (OPT_PROJECT_GRANTS) can be used instead. Unconfigured or unavailable means denied, never allowed.
  3. The model never sees unverified text. Doc0SourceClient verifies version, tenant, checksum, segment hashes and that the index was not truncated before anything is sent to INF0.
  4. Every returned value must point at an exact span in the source; otherwise the result is needs_review and nothing is exported.
  5. Credits are reserved before the model runs and settled to measured usage. An unknown model outcome keeps the reservation for reconciliation instead of releasing it.
  6. No price is ever read from the client. The price book is a versioned, server-owned JSON document (OPT_CREDIT_PRICE_BOOK).
  7. Pilot mode is explicit. OPT_CREDITS_MODE=off skips reserve/settle and reports credits: null. It is not a silent fallback and it does not enable purchases; unset/on requires the price book and the ledger.

Reusable packages

  • packages/siax-access (@siax/access) — framework-neutral OpenFGA relationship checks and a fail-closed isProjectAccessAllowed. Intended for DOC0, N0D and every other SIAX product instead of per-repo copies of project-relationships.ts.
  • src/document-workbench/* — upload coordination, DOC0 source reading, INF0 admission, structured-output validation, export and credit ledger. These modules have no Next.js imports and can be lifted into a published package unchanged.

Which AI path is used

createInf0Gateway() prefers the live LiteLLM gateway (INF0_GATEWAY_LITELLM_KEY, https://ai-gateway.siax.io/v1, OpenAI-compatible) and falls back to the native INF0_URL surface. Admission — tenant/project scope, model allow-list, data class, external-transfer policy and token limits — runs locally in both paths before the request leaves the process.

What is still deliberately not claimed

  • The output is mechanically_validated, not proven complete or semantically correct. OCR quality and coverage require review.
  • Refunds and chargebacks do not automatically revoke credits; no such handler is wired.
  • Format breadth is limited by DOC0's parser. A file DOC0 cannot parse is reported, never guessed.