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
- 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.
82 lines
4.8 KiB
Markdown
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.
|