Files
0pt/docs/guides/document-workbench.md
admin dade37ee05
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 4s
masterplan-lock / check (push) Successful in 5s
CI (SIAX Cloud) / security (pull_request) Successful in 16s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 16s
CI (SIAX Cloud) / integration (pull_request) Successful in 9m58s
CI (SIAX Cloud) / quality (pull_request) Successful in 10m25s
feat(credits): one-time trial grant so the ledger runs in production
- Ledger accepts kind='grant' (a server-owned balance grant, not a payment).
- OPT_TRIAL_CREDITS=N grants N credits once per tenant (idempotent key
  trial:<tenant>) on balance/structure, so reserve -> settle is exercised
  before any customer purchase.
- sql/020 extends the kind constraint (re-runnable).
- Tests for the grant helper; full suite 1345 passed.
2026-11-02 22:05:06 +01:00

82 lines
4.8 KiB
Markdown

# 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. **Trial credits are a grant, not a payment.** `OPT_TRIAL_CREDITS=N` grants N
credits once per tenant (`kind='grant'`, idempotency key `trial:<tenant>`),
so the ledger is fully exercised before any real purchase. It is never read
from a client and never looks like a Stripe payment.
8. **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
Order:
1. **Decision server model** (`DECISION_OLLAMA_URL` + `DECISION_OLLAMA_MODEL`) —
the self-hosted Ollama on server-decision-01, used while the external model
API is on hold. Internal only, no provider key, no per-token cost.
2. **Live LiteLLM gateway** (`INF0_GATEWAY_LITELLM_KEY`).
3. Native `INF0_URL` surface.
Admission — tenant/project scope, model allow-list, data class,
external-transfer policy and token limits — runs locally in every path before
the request leaves the process. Evidence spans are computed deterministically
from the returned values when a small local model omits offsets: a value must
occur exactly once in the source, otherwise the result is `needs_review`.
## 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.