# Invoice Processor — App Manual

## Contract

- App ID: `wp_invoices`
- App version: `1.0.0`
- Platform contract: `1.0`
- Provider: internal AI extraction service

`APP_MANIFEST` in `__init__.py` is the machine-readable source of truth.

This migration is incremental. Only capabilities present in `APP_MANIFEST` are
Standard v1 capabilities. Planned actions are not advertised before their
implementation and contract tests exist.

## Exported capabilities

### `platform.health`

Action: `health_v1`

This synchronous, read-only operation reports local app readiness. It does not
read an invoice, contact an AI provider, consume AI quota, or inspect another
app.

Success data:

```json
{"healthy": true, "ai_checked": false}
```

### `invoice.document.submit`

Action: `submit_v1`

This action accepts one base64-encoded PDF, JPEG, PNG, or WebP document up to
15 MB. It validates the declared MIME type against the file signature, writes
the original through tenant-aware quota storage, runs centralized AI extraction
and token accounting, persists the canonical JSON, and returns the completed
result plus opaque artifact IDs.

The current implementation is synchronous and may take up to 300 seconds. An
`idempotency_key` of 8–128 characters is required. Reusing it for the same
tenant resolves to the same logical `invoice_id` and storage locations.

Input:

```json
{
  "filename": "invoice.pdf",
  "content_type": "application/pdf",
  "file_bytes_base64": "JVBERi0xLjQK...",
  "idempotency_key": "wp-invoice-20260724-001",
  "engine": "mini"
}
```

Success data contains:

```json
{
  "invoice_id": "inv_...",
  "status": "completed",
  "result": {},
  "artifacts": {
    "original": "invoice:inv_...:original",
    "result_json": "invoice:inv_...:result-json"
  }
}
```

`submit_trusted_v1` preserves the upload envelope while returning result
contract 2.0, its deterministic `review_status`, and a safe supplier-profile
state. `result_v1` reads a stored result by opaque invoice ID within trusted
tenant context. `confirm_supplier_profile_v1` requires an authenticated user
and activates only the allowlisted corrections associated with that invoice.
Durable `status_v1` and exports remain future contract work.

### Canonical invoice result

The stable result schema is defined in:

```text
contracts/invoice_result.v1.schema.json
```

It contains the invoice identifier and processing status, safe document
metadata, normalized supplier/invoice fields, currency, line items, subtotal,
tax, total, deterministic validations, stable warning codes, confidence, and
processing metadata. It never contains model reasoning, raw evidence, document
bytes, tenant IDs supplied by clients, or physical server paths.

The reusable `extract_canonical_invoice()` boundary preserves trusted gateway
context for centralized AI usage accounting and converts the current prompt
output to this schema. Mathematical checks are recalculated in application code
instead of trusting validation flags generated by AI.

## Error contract

| Code | HTTP | Retryable | Meaning |
|---|---:|---|---|
| `action_not_found` | 404 | No | The requested Standard v1 action is unsupported. |
| `invalid_input` | 400 | No | Required submission data is absent or invalid. |
| `unsupported_document_type` | 400 | No | MIME type or file signature is unsupported. |
| `document_too_large` | 413 | No | Decoded document exceeds 15 MB. |
| `storage_quota_exceeded` | 413 | No | Tenant storage cannot accept the document. |
| `ai_quota_exceeded` | 429 | No | Remaining tenant AI allowance cannot cover the document reservation. |
| `extraction_failed` | 502 | Yes | Extraction failed without exposing provider details. |
| `invoice_not_found` | 404 | No | The invoice is absent from the trusted tenant namespace. |
| `confirmation_identity_required` | 403 | No | Confirmation has no authenticated user identity. |
| `supplier_profile_not_found` | 404 | No | No confirmable profile belongs to that invoice. |

Messages are safe for clients. Responses do not expose document contents, API
keys, stack traces, provider exceptions, or internal filesystem paths.

Visual AI requests reserve 50,000 tenant tokens before contacting the provider.
Provider failures release the reservation. Successful responses reconcile it
to exact reported usage, including a signed downward adjustment when actual
usage is lower. Usage that has already occurred is never rejected retroactively.

## Calling through HTTP

Endpoint:

```text
POST https://<flask-host>/flask/api/v1/wp_invoices/health_v1
```

Replace `health_v1` with `submit_v1` for document submission and send the JSON
body documented above.

Required headers:

```text
Authorization: Bearer <RS256 site JWT>
Content-Type: application/json
X-App-ID: wp_invoices
```

The JWT must identify an entitled tenant/site and contain the gateway scope for
invoking `wp_invoices`. The response uses the Standard v1 envelope and includes
the gateway `request_id`.

Custom WordPress PHP uses only the public Bridge helper:

```php
$bytes = file_get_contents('/path/controlled-test-invoice.pdf');
$result = wp_flask_bridge_invoke(
    'wp_invoices',
    'submit_v1',
    [
        'filename' => 'controlled-test-invoice.pdf',
        'content_type' => 'application/pdf',
        'file_bytes_base64' => base64_encode($bytes),
        'idempotency_key' => 'wp-test-20260724-001',
        'engine' => 'mini',
    ]
);
```

The file path in this snippet is local to trusted WordPress PHP and is never
sent to Flask. Production UI code must validate uploads before reading them.

## Security and tenant isolation

- Tenant and identity values come only from trusted gateway context.
- Invoice payloads must never select a tenant or storage namespace.
- The app declares no outbound capabilities and must not call ABN Lookup or any
  other app as part of its Standard v1 responsibilities.
- Invoice documents and extracted financial data are classified as restricted.
- Logs must omit document bytes, extracted invoice fields, credentials, JWTs,
  internal paths, and raw provider errors.

The legacy `process` action still contains migration-only ABN/PDF behavior for
an active consumer. It is not a Standard v1 capability and will be retired only
after that consumer moves to the public invoice contract.

## Supplier layout memory (v2 foundation)

An invoice may stage a supplier profile candidate only after the deterministic
quality gate accepts it and sets `can_teach_supplier_profile: true`. Candidates
are inert. A signed-in user must explicitly confirm allowlisted layout
corrections before the profile becomes `active`.

Profiles are isolated by trusted tenant context and contain only layout metadata:
document type, recognized labels, item-column names, and fixed behavioral flags.
They do not retain product descriptions, amounts, invoice numbers, document
bytes, credentials, or free-form AI instructions. An active profile is never
replaced by an automatically generated candidate. Hints are bounded and
sanitized before later use by extraction.

The correction input contract is
`contracts/supplier-profile-confirm.v1.schema.json`; the stored record contract
is `contracts/supplier-profile.v1.schema.json`. Public confirmation and profile
selection actions belong to the upcoming v2 submit/result API block.

## Observability

Structured events are written to:

```text
logs/bridge_platform/wp_invoices.log
```

`wp_invoices_health_checked`, `wp_invoices_submission_completed`, and
`wp_invoices_submission_failed` record request ID, tenant ID, safe outcome and,
on success, the opaque invoice ID. They do not log document data, filenames,
extracted fields, base64, provider responses, or internal paths.

## Testing and validation

Run:

```text
venv/bin/python scripts/validate_app_contract.py wp_invoices
venv/bin/python -m unittest discover -s apps/wp_invoices/tests -p '*_checks.py'
```

The checks cover manifest validity, health behavior, stable unknown action
errors, legacy dispatch compatibility, input validation, base64 decoding,
canonical schema conformance, public-field redaction, normalization, stable
warnings, deterministic arithmetic, and propagation of trusted AI context.
Later blocks must add durable-job, status, authorized-download, and export
contract tests.

Manual health check through the bridge:

```php
$result = wp_flask_bridge_invoke('wp_invoices', 'health_v1', []);
```

Confirm that the response has `status: ok`, the expected `request_id`, and
`data.healthy: true`.

## Optional WordPress App Pack

- Package ID: `wp_invoices_wp`
- Development version: `1.1.6-dev`
- Shortcode: `[ams_invoice_processor]`
- PHP helper: `ams_invoice_submit_uploaded_file($file)`
- Capabilities: `invoice.document.submit_trusted`, `invoice.result.read`, and
  `invoice.supplier_profile.confirm`

The shortcode is available only to signed-in WordPress users. It validates a
WordPress nonce, upload status, file size, extension and MIME detection before
calling the public Bridge helper. The browser receives neither a JWT nor an AI
key. Result fields are escaped and displayed as a summary, line-item table,
totals, deterministic validations, warnings, invoice ID, and support reference.

The form shows a processing state while the synchronous request is running.
Bridge Core defaults to 25 seconds; this trusted App Pack requests a bounded
120-second timeout for invoice submission. The public helper caps overrides at
300 seconds. Production-scale processing will still move to durable submit and
status jobs rather than holding the browser request open.

Development source is not installable through the release catalog. Before a WP
test, build and sign the pack, register its immutable release for the entitled
site, install it from **WP Flask Bridge → Apps**, activate it, and add:

```text
[ams_invoice_processor]
```

No custom test plugin or PHP snippet is required.

## Versioning and compatibility

- App version: `1.1.6`.
- Supported Platform contract major: `1`.
- Additive optional fields may be introduced in minor releases.
- Required-field or semantic changes require a new versioned action.
- Legacy actions are excluded from `APP_MANIFEST` and receive no Standard v1
  compatibility guarantee.
- `test` and `process` remain temporarily available because an active mail
  consumer still relies on their legacy response shape.

### Result contract v2

The v2 trust gate is exposed by `submit_trusted_v1`. It adds invoice
type, explicit `accepted`/`needs_review` status, item coverage, four-state
validations, expected/calculated differences, categorized issues, and a strict
`can_teach_supplier_profile` decision. Incomplete results can never create or
update a supplier profile. The ALDI regression fixture is required to produce
`needs_review` with approximately 3.9% item coverage.

The v2 extractor uses `prompts/extractor.unified.v2.md` and
`contracts/extraction.unified.v2.schema.json`. One schema-bound vision request
classifies POS/Corporate and transcribes all fields and line items. Arithmetic
instructions and model-generated validation flags were removed from the prompt;
the deterministic trust gate owns those decisions. The original `submit_v1`
remains available for compatibility, while the WordPress App Pack uses the
trusted action.

When the first pass has no line items or less than 98% subtotal coverage, v2
runs one schema-bound recovery request limited to the line-item region. The
candidate replaces the first pass only when its deterministic quality score
improves. Correct first-pass results never spend recovery tokens. Failed or
worse recovery preserves the first result as `needs_review` and never exposes
provider exceptions.

## Scope boundary

This app accepts PDF/image invoices, extracts and normalizes invoice data,
performs structural and mathematical validation, accounts for tenant AI/storage
usage through platform services, and exposes stable results and exports.

It does not perform ABN lookup, email intake/delivery, review-PDF generation,
branding, accounting decisions, or direct AroFlo/provider integration. Those
belong to external workflows or separate apps.
