fix: harden week-two ordering, artwork and operations

This commit is contained in:
Cauê Faleiros
2026-09-23 10:40:18 -03:00
parent ccc25a2d5d
commit 24013458c9
43 changed files with 1064 additions and 228 deletions

View File

@@ -297,11 +297,12 @@ as references and are not imported or started by Compose.
files can be quoted, commercially approved, paid, downloaded, attached as final
files, or admitted to the print queue. Rejected/error files remain blocked and
expire within three days. The isolated scanner uses signatures bundled in its
pinned image and has no external network route. The transport accepts files up
to 5 GiB, but the local scan/release limit is 128 MiB; larger files remain
blocked. This malware gate is not print pre-flight or artwork validation.
pinned image and has no external network route. The multipart transport could
carry 5 GiB, but API and customer admission stop at the effective 128 MiB
scan/release limit by default. Larger files require a new scan/release design.
This malware gate is not print pre-flight or artwork validation.
- A retention worker removes expired object bytes but keeps order/file metadata:
incomplete uploads after one day, originals within seven days of manual final
incomplete uploads after a one-hour reservation lease, originals within seven days of manual final
artwork approval, and attached final/correction files within 30 days of the
order's first upload. Storage lifecycle is also a 30-day backstop.
- Structured security events are written to logs and PostgreSQL. The local
@@ -327,7 +328,7 @@ as references and are not imported or started by Compose.
not been deployed. Its fail-closed preflight intentionally rejects the current
source until production adapters, Docker-secret file loading, approved inputs,
restore rehearsal, image scans, and human security approval are complete.
- `python3 -m app.backup create-and-verify` creates a private, Git-ignored bundle
- `python3 -m ops.backup create-and-verify` creates a private, Git-ignored bundle
containing a PostgreSQL dump plus every complete, unexpired object already marked
`clean`. SHA-256 manifests protect both parts. Verification restores the database
under a UUID name and the object bytes under a UUID MinIO prefix, hashes the

View File

@@ -161,8 +161,8 @@ test is unmistakable:
```bash
python3 -m tests.security_test
python3 -m tests.scanning_test
docker compose exec -T api python3 -m tests.runtime_security_test
docker compose exec -T api python3 -m tests.retention_test
docker compose -f compose.local.yaml exec -T api python3 -m tests.runtime_security_test
docker compose -f compose.local.yaml exec -T api python3 -m tests.retention_test
```
`local.scanning_test` stores an EICAR fixture as `SECURITY-EICAR.cdr`; ClamAV
@@ -183,14 +183,15 @@ test order for inspection. Both integration scripts read `.env` automatically.
## Configuration and storage
`.env.example` lists local ports, database/MinIO values, operator login, adapter
selection, mock freight amount, maximum file size (5 GiB), and multipart size
(8 MiB by default). The malware scanner releases only files up to 128 MiB by
default (`SCAN_MAX_BYTES`); larger uploads remain blocked even though the
multipart transport supports 5 GiB. `S3_ENDPOINT=http://storage:9000`, database
selection, mock freight amount, transport ceiling (5 GiB), and multipart size
(8 MiB by default). The API and customer picker admit only files within the
malware scanner's effective 128 MiB limit by default (`SCAN_MAX_BYTES`); the
larger-file path remains a Week 2 decision. `S3_ENDPOINT=http://storage:9000`, database
hostname `db`,
and the internal service ports are fixed Compose wiring. The public S3 endpoint
must resolve from the browser; keep `http://localhost:9000` for this stack.
Parts use 15-minute presigned URLs and uploads must finish within one day.
Parts use 15-minute presigned URLs and unfinished reservations expire after
one hour. Clients can cancel a reservation through the upload DELETE endpoint.
Only fake integration adapters and `s3-local` storage are accepted. Startup fails
if a production adapter/environment or nonlocal S3 endpoint is selected.
@@ -199,7 +200,8 @@ and MinIO also join a network that permits loopback port publishing.
Objects are private, use UUID keys rather than filenames, and persist in a named
volume. MinIO lifecycle rules expire objects after 30 days and abandon incomplete
multipart uploads after one day; the API also blocks expired downloads. Order
multipart uploads after one day as a backstop; the API lease and worker release
unfinished reservations after one hour. The API also blocks expired downloads. Order
history remains in PostgreSQL. Completed files start in `pending`; unknown,
scanner-error, over-limit, encrypted/unsafe, and malware results fail closed.
Only `clean` files can cross quote, payment, download, final-approval, and queue
@@ -217,7 +219,7 @@ again. The operator can still inspect order records.
## Local backup and restore check
```bash
python3 -m app.backup create-and-verify
python3 -m ops.backup create-and-verify
```
This writes a private four-file bundle in `backups/` (ignored by Git and Docker
@@ -234,7 +236,7 @@ checks database counts, bundle hashes, every archived object's hash, and the byt
downloaded after restore, then removes only the temporary database and objects. It
never restores over active data. Keep every bundle file private: it contains
customer data, password hashes, and customer artwork. To verify it again, run
`python3 -m app.backup verify` followed by the printed
`python3 -m ops.backup verify` followed by the printed
`backups/...manifest.json` path. Legacy database-only `.dump` backups remain
verifiable. Scheduling, offsite copies, and a production restore runbook remain
unfinished.
@@ -242,7 +244,7 @@ unfinished.
After building the current image, test retention with synthetic files:
```bash
docker compose exec -T api python3 -m tests.retention_test
docker compose -f compose.local.yaml exec -T api python3 -m tests.retention_test
```
This checks that expired bytes are removed while unexpired files survive. It
@@ -251,14 +253,14 @@ cleans up its own synthetic object bytes and retains their metadata.
## Operations and troubleshooting
```bash
docker compose logs --tail=100 api worker scanner
docker compose restart api worker
docker compose ps
docker compose down
docker compose -f compose.local.yaml logs --tail=100 api worker scanner
docker compose -f compose.local.yaml restart api worker
docker compose -f compose.local.yaml ps
docker compose -f compose.local.yaml down
```
`down` stops the stack and preserves named database/storage volumes. Restart
with `docker compose up -d --wait`. Do not add `--volumes` unless you intend to
with `docker compose -f compose.local.yaml up -d --wait`. Do not add `--volumes` unless you intend to
permanently erase all local orders and artwork. No reset is required for tests.
Seven long-running services have Docker health checks; the database and storage
@@ -270,7 +272,7 @@ exposes `/minio/health/ready`.
Run the redacted local alert summary inside the API network namespace:
```bash
docker compose exec -T api python3 -m app.security_status
docker compose -f compose.local.yaml exec -T api python3 -m ops.security_status
```
Exit status 1 means attention is required. Review blocked artwork, rate limits,
@@ -312,7 +314,7 @@ a direct pin, regenerate the lock in the same Python 3.12 environment and rebuil
```bash
./infra/lock_dependencies.sh
docker compose up --build -d --wait
docker compose -f compose.local.yaml up --build -d --wait
```
The generator downloads public package metadata in a disposable container and

View File

@@ -0,0 +1,85 @@
# DTF remediation register — 2026-09-22
This is the action list for all 37 findings in [the September 21 review](REVIEW-2026-09-21.md). That review contains the evidence and severity for each ID. This register includes the September 22 payment review. It is a plan, not evidence that a finding has been closed in production.
**Current position:** We are in Week 2. The local fixes for foreign-quote order disclosure and approval without a verified amount are implemented and tested, but are not committed or deployed. The first order-correctness slice now covers parts of findings 2, 5–8, and 11; see the progress note below. The remaining work and production verification stay open. Payment webhook work is partial progress on finding 31, not completion of real payments.
**Local progress, 2026-09-22:** Browser and API regressions covered stale editor items, DPI refusal and warning acknowledgement, changed-cart quote actions, oversized width, and finals invalidated by a later correction. A versioned per-file source specification survived quote review into the order snapshot. At that point exact placement coordinates and a generated print file were still missing. None of these changes is a production release.
**Local progress, 2026-09-23:** Specification v2 adds per-copy film coordinates, validates every copy and the quote height, and keeps a downloadable layout manifest in the approved order. The board now pages pending and approved unpaid quotes; a local regression reached all 105 pending and 22 approved fixture quotes. Final print-file generation, completed-order search, and quote cancellation/expiry lifecycle remain open.
**Operational progress, 2026-09-23:** Finding 23's entrypoints are repaired locally. The staging gate passed in a network-disabled image with non-secret fixture data; `ops.security_status` ran in the API image and correctly reported stale local signatures; `ops.backup create-and-verify` restored database counts and 78 clean objects in isolated temporary targets; the production API image built and imported `ops`. This verifies the commands, not production offsite recovery (finding 30) or a fresh scanner (finding 17).
**Quality progress, 2026-09-23:** Findings 9 and 10 are partly repaired: failed image decoding blocks checkout, mixed analyzed/manual sheets stay at table price, and rotated DPI uses the correct pixel axis. PDF measurement now uses the PDF.js page model with effective crop, rotation, UserUnit and page count; invalid or multi-page files block quoting. The same-origin PDF worker and isolated browser checks pass. Unsupported image operators, representative print-file evidence and final printability remain open.
**Upload progress, 2026-09-23:** The API and customer picker now reject files above the effective ClamAV stream limit before transfer, and the session advertises that limit. Unfinished reservations have a one-hour lease and a customer/operator cancel endpoint; owner-scoped cancellation has an integration check. Quota remains reserved until the object is actually purged, so a failed cleanup cannot admit unaccounted storage. PDF rendering now destroys the parser job when its timeout fires, covered by a browser check. This closes the misleading upload-then-quarantine path locally but does not satisfy the agreed large-file capability in finding 3. A tested large-file scan/release design, stronger anonymous admission controls (finding 16), and remaining browser resource bounds (finding 12) are still required.
## Gates
| Gate | Meaning |
|---|---|
| **W2** | Fix during Week 2 before calling the corresponding client workflow complete. These defects can be worked on while provider contracts are clarified. |
| **Upload** | Resolve before inviting the public to upload customer artwork. |
| **Paid** | Resolve before enabling live checkout or accepting a real paid order. |
| **Release** | Resolve before declaring the deployed production system ready. |
| **Incremental** | Improve alongside feature work; it does not justify a standalone rewrite. |
The gates are cumulative: a live release must pass W2, Upload, Paid, and Release checks. Decisions labelled **business** require an agreed product rule; engineering can build and test the surrounding flow in parallel. Where a deployment risk is conditional, verify the actual topology and document the result before closing it.
## Complete finding-to-action map
| Review ID | Gate | Required action and closure evidence |
|---|---|---|
| 1 | Paid | Implement real payment, freight, ERP, and notification adapters with sandbox acceptance and reconciliation; remove fake adapters from the live path. |
| 2 | W2 | Store a versioned per-file production specification and approved layout on quote and order; prove the factory can reproduce the purchased job. |
| 3 | Upload; business | Agree the advertised maximum and implement a scan/release path that actually supports it; reject unsupported sizes before transfer. |
| 4 | W2 | Give quotes an explicit lifecycle and paginated/searchable operator view; verify the 101st actionable quote remains visible. |
| 5 | W2 | Invalidate or revision-bind finals when a new correction arrives; test a correction submitted after a final was uploaded. |
| 6 | W2 | Make quality eligibility a checkout gate and store any required acknowledgement against the artwork revision. |
| 7 | W2 | Clear the current cart item immediately when artwork is removed or becomes invalid; test the submitted payload. |
| 8 | W2 | Bind checkout to an immutable quoted cart snapshot; require re-quote after any material edit, including same-price edits. |
| 9 | W2 | Measure PDF pages through the parser's page model; handle every supported page or reject multi-page/unsupported geometry explicitly. |
| 10 | W2 | Require quality evidence per billable source; make undecodable/unknown sources explicit and correct rotation-sensitive DPI calculations. |
| 11 | W2 | Validate physical dimensions before packing; never silently scale a requested print size. |
| 12 | Upload | Bound browser decoding, copy count, PDF work, and preview size; cancel obsolete work and test representative large inputs. |
| 13 | Paid | Capture and validate a full delivery-address snapshot, then connect it to freight quote and order fulfilment. |
| 14 | Paid; business | Set written auto-approval rules and manual-exception criteria; prove eligible orders can complete after hours without an operator. |
| 15 | W2; business | Define accepted print output, generate it from the approved versioned layout, and compare produced geometry/metres with the quote. If scope changes, update the client commitment and site claims explicitly. |
| 16 | Upload | Limit anonymous reservation capacity and lifetime; add cancellation and cleanup, then test quota-exhaustion behavior. |
| 17 | Upload | Update ClamAV signatures on a controlled schedule; surface signature age and fail the intake gate when stale. |
| 18 | Release | Trust only the actual proxy hop, restrict origin access, and test real client IP/rate limits through the deployed Swarm topology. |
| 19 | Release | Wire file-backed secrets into the active stack; give each service only necessary credentials and remove unused bootstrap secrets. |
| 20 | Release | Recheck operator `active` atomically when issuing and using sessions; test disable-versus-login concurrency and document password-change session policy. |
| 21 | Paid | Provide email verification and customer recovery/guest continuity, and make checkout's account-creation claim match reality. |
| 22 | Release; business | Agree retention/export/deletion rules for profiles, quotes, orders, payloads, artwork, and backups; implement and verify them. |
| 23 | W2 | Repair staging, backup, and security commands after the directory move; smoke-test them in the images and Compose files actually shipped. |
| 24 | Release | Set and test PostgreSQL node placement/persistence for the intended Swarm size, plus recovery after host failure. |
| 25 | Release | Scan before promotion, publish immutable paired API/web image identities, and deploy exactly the scanned release. |
| 26 | Release | Make preflight enforce the active stack contract and provider behavior; verify Portainer/deployment convergence after promotion. |
| 27 | Release | Run browser tests in a network where signed storage URLs work; fail CI when Chrome or the test endpoint is unavailable. |
| 28 | Release | Isolate each CI Compose project, ports, networks, and volumes; serialize release promotion and test overlapping runs. |
| 29 | Release | Separate liveness/readiness, monitor provider backlog, cleanup, scanner freshness and backup age; test alert routing and rollback acceptance. |
| 30 | Release; business | Set recovery objectives, make consistent encrypted offsite backups, and rehearse restore of database plus required live artwork. |
| 31 | Paid | Finish durable payment intent, idempotent webhook handling, status/refund rules, reconciliation, and ordered outbox/dead-letter recovery; test provider-success/database-failure cases. Signed event work is only partial progress. |
| 32 | Upload | Bound upload concurrency, decouple upload from scan waiting, measure queue latency, and distinguish transient scan errors from rejected content. |
| 33 | Release | Introduce ordered schema migrations and core constraints/relationships; test both clean install and upgrade from the existing schema. |
| 34 | Incremental | Replace shared mutable browser cart state as part of IDs 2/7/8; then extract reusable business operations from routes and add bounded DB connection management where load measurements warrant it. |
| 35 | Release | Add representative artwork, real PDF, failure/retry, migration, operational-command, and exact-release acceptance tests. |
| 36 | W2 | Correct executable setup/Portainer/security instructions and PDF generator paths; check generated output against current scope. |
| 37 | Release | Inventory and scan every deployed image and vendored asset, pin release dependencies, and set a controlled refresh process. Do not describe the existing PDF.js advisory as a proven exploit. |
## Execution order
1. **Correct the customer/order model now:** IDs 2, 4–11, 23, and 36. Keep an immutable quote revision through payment, production output, and correction approval. Close each defect with a focused regression test and a real artwork example where geometry matters.
2. **Set the missing product rules while coding continues:** IDs 3, 14, 15, 22, and 30. Obtain representative files, accepted print format, auto-approval thresholds, retention rules, recovery objectives, and provider sandbox access. Do not collect credentials in this document.
3. **Make public intake safe:** IDs 3, 12, 16, 17, and 32. Test the declared upload size end to end, including scan, release, quota, browser memory, and timeout behavior.
4. **Complete live commerce:** IDs 1, 13, 14, 15, 21, and 31. Build freight/address, payment/reconciliation, ERP, and notification flows; test duplicates, outages, refunds, and human exceptions in provider sandboxes.
5. **Prove the deployed system:** IDs 18–20, 22, 24–30, 33, 35, and 37. Run the exact images and stack, exercise migration, backup/restore, monitoring, secrets, CI and release rollback. Improve ID 34 as the affected areas are changed.
## Who supplies what
- **Engineering:** implement and test the code, schema, operational commands, CI gates, provider adapters, and recovery runbooks; gather evidence for each closure. This work can start with the order/cart defects without waiting for provider access.
- **Business/client:** approve unattended-pricing exceptions, final print-file format and samples, the real maximum file size, shipping services and policy, privacy retention, and recovery objectives. The detailed worksheet is [production inputs](PRODUCTION_INPUTS.md).
- **Provider/operations owners:** supply sandbox accounts and configuration through the approved secret channel, plus the real deployment topology, backup destination, alert recipients, and release/rollback ownership. No credentials belong in this register or the repository.
**Closure rule:** A checkbox or passing mocked flow is insufficient. For each ID, keep the original evidence, record the implemented change and test, then verify in the environment that carries the risk. The [working roadmap](ROADMAP.md) tracks Week 2 delivery status; this register tracks the full defect disposition.

View File

@@ -7,18 +7,37 @@
> Update the **Current step** line and the item status every time something moves.
> Add new findings at the bottom of the relevant block rather than rewriting history.
**Current step:** Block 0 closed; Block 2 closed except 2.9–2.11; 4.1, 4.2, 4.5,
5.1, 5.3, 5.4 and 5.8 done. Remaining work needs decisions (3.1, 3.2, 3.3) or
client inputs (1.1, 1.2, 2.10). Block 1 still waits on client inputs for
1.1/1.2.
**Current step (2026-09-23, Week 2):** Payment safety fixes 2.13 and 2.14 and
the local order-correctness work in 3.6/3.9 have passed integration checks.
Production specification v2 now records each copy's film coordinates and is
kept through the approved order; 4.6 now pages pending and approved unpaid
quotes, including a tested 101st pending quote. Operational entrypoints in
5.12 are repaired and locally exercised. Image decoding, mixed-sheet grading,
rotation-sensitive DPI, and PDF page geometry are corrected in 3.9/4.4.
Next address the upload/scanner safety gate and unsupported PDF image evidence.
The customer/API upload admission now stops above the scanner's effective limit
before transfer; the 5 GiB large-file product path still needs agreement and
implementation.
Unfinished upload reservations now expire after one hour or can be cancelled
explicitly; anonymous admission and browser resource bounds stay open.
Generated print output, lifecycle/recovery, and provider work remain open.
Obtain decisions for unattended pricing, print-file acceptance and large files,
plus sandbox inputs for freight and Mercado Pago. Week 2 delivery items 1.1–1.5
remain open; 1.6 is complete.
> Paths in closed items are written as they were when the finding was made.
> The repository was laid out by role on 2026-09-21 (`local/` became `app/`,
> with `tests/`, `ops/`, `infra/` and `web/` beside it); the history is left
> as recorded rather than rewritten.
**Last audit:** 2026-09-18, full read of `app/`, `dtf-site.html`, `deploy/`,
`.gitea/`, docs and legacy prototypes. Findings below carry their audit IDs.
**Last audit:** 2026-09-18, full read of the then-current tree. The 2026-09-21
full review is `docs/REVIEW-2026-09-21.md`; the 2026-09-22 review and payment
probes added new findings to Blocks 2–5 below. Historical paths in closed items
remain as recorded.
**Full remediation register:** `docs/REMEDIATION-2026-09-22.md` maps every one
of the 37 review findings to an action and a release gate. Use it alongside
this Week 2 tracker; a green milestone here does not close the production gate.
| Status | Meaning |
|---|---|
@@ -29,6 +48,29 @@ client inputs (1.1, 1.2, 2.10). Block 1 still waits on client inputs for
---
## Week 2 execution sequence
This sequence keeps the client commitments in Block 1 visible while correcting
defects that would make those commitments unsafe or impossible to operate.
Do not mark a provider item complete from a fake-adapter test or a healthy page.
| Order | Work | Exit evidence |
|---|---|---|
| 1. Immediate safety — done 2026-09-22 | Close 2.13 and 2.14; cover foreign quote IDs, missing/invalid amounts, duplicates and valid approvals. | Local integration checks pass and no other customer's order is returned. |
| 2. Order correctness | Fix 3.6 and 4.6: one current cart/quote snapshot, versioned per-file production instructions, correction/final revision binding, visible actionable quotes. | The approved quote, order and final file can be traced back to the same reviewed layout; edits cannot buy an old cart. |
| 3. Resolve product contracts | Decide 3.1–3.3: which quotes may auto-approve, what generates the print file, and which sizes the upload and scanner can release. | Written acceptance rules and representative artwork/large-file cases before enabling unattended payment. |
| 4. Week 2 integrations | Add destination data and real freight first, then Mercado Pago payment intents/webhooks/reconciliation, then Tiny/Olist order creation. Keep the four agreed WhatsApp events in the same delivery contract. | Sandbox flows and failure/retry cases pass; no fake provider is presented as production ready. |
| 5. Operability and release | Repair 5.12–5.14, signatures, proxy trust and backup/restore; gate browser tests and the exact deployed images. | Fresh install, upgrade, recovery and deployed release checks pass with alert ownership recorded. |
Client inputs needed for steps 3–4 are listed in `docs/PRODUCTION_INPUTS.md`.
Engineering can complete steps 1–2 and repair local operational commands while
those inputs are gathered. The full disposition of architecture, security,
quality, operational, and delivery findings is in
`docs/REMEDIATION-2026-09-22.md`; all release gates there must be met before
accepting real customer work.
---
## Block 0 · Broken right now
Nothing in this block is optional. Until it is closed, the system cannot be
@@ -158,29 +200,28 @@ Ports 8090/8091/8010 were used; 8080 was held by an unrelated preview server.
From the report already sent. These are dated promises, not backlog.
- `[~]` 1.1 — Mercado Pago transparent checkout, signed and idempotent webhooks.
**The provider-independent half is built** (2026-09-22): `POST /api/payments/webhook`
verifies a signature before parsing, records every delivery under the provider's
own event id, and applies it in one transaction. A duplicate is a no-op, a
re-sent approval finds the order already there, and an approval whose amount
disagrees with the reviewed quote is refused rather than shipped. Order creation
moved to `app/payments.py` so the webhook and the local checkout cannot drift.
Exercised end to end by `tests/payment_test.py` against a fake signer.
What remains needs the client: a sandbox account, webhook administration, the
event/status mapping and the refund policy. In code it is one adapter supplying
`create`, `verify` and `parse` — nothing in the service changes.
**Current foundation (2026-09-22):** a fake signer exercises signature rejection,
event-ID deduplication, amount comparison and transactional order creation.
This is not a Mercado Pago integration. Complete a durable payment intent,
provider payment ID and currency binding, real verification and status lookup,
delayed/duplicate event handling, refund/cancellation rules and reconciliation.
A refused paid event must be visible for operator resolution rather than silently
treated as finished. See 2.14 and 3.7. Requires sandbox access, webhook
administration, event mapping and an approved refund policy.
- `[ ]` 1.2 — Real freight quotation. **Blocked on client inputs** (see
`PRODUCTION_INPUTS.md`): source platform, credentials, origin CEP, services,
packaging weight/dimensions per length, subsidy policy.
- `[ ]` 1.3 — Idempotent Tiny/Olist order creation with order-number traceability.
Confirm endpoints, tag behaviour and rate limits first.
- `[ ]` 1.4 — Final print-file generation (see 3.2 — this is the same problem).
- `[ ]` 1.4 — Final print-file generation (see 3.2 and 3.6: production instructions
must survive checkout before an output engine can reproduce the approved job).
- `[ ]` 1.5 — Main Kanban production states consolidated.
- `[ ]` 1.6 — **Block 0.2 + 0.3**, promised as "início da próxima semana".
- `[x]` 1.6 — **Block 0.2 + 0.3** were completed and verified on 2026-09-18.
`[!]` The production compose currently blocks `dev_paid` (`ENVIRONMENT != 'local'`)
and ships only fake adapters, so the deployed system cannot take an order at all.
1.1 is what unblocks it.
Real freight, payment initiation and verified provider events are required to
unblock it; the fake webhook alone does not.
---
@@ -335,8 +376,36 @@ proving control of the e-mail. Needs a transactional mail provider — **client
### `[ ]` 2.11 — LGPD `(F15)`
CNPJ, phone and e-mail are kept indefinitely in `accounts.profile` and
`orders.snapshot`. Artwork has a 30-day policy; personal data has none, and there is
no privacy notice, consent record or deletion path.
`orders.snapshot`. Artwork has a 30-day policy; personal data has no defined
retention, export or deletion path. The Site links an external privacy notice;
confirm that it covers this processing and define the required records and
customer rights flow before production activation.
### `[x]` 2.13 — A foreign paid quote ID exposes an order (2026-09-22 review)
The `dev-paid` refusal fallback fetched `orders` by `quote_id` without `owner`.
A separate local customer session received the full paid order when supplied
another customer's quote ID. `app/api/orders.py` now includes the owner in the
fallback query. The local payment integration test confirms a foreign ID returns
404 while the owner can still retrieve the already-paid order.
### `[x]` 2.14 — A signed approval without a paid amount creates an order
`app/payments.py` compared amounts only when the event contained one. A local
signed `approved` event without `amount_cents` created an order. The service now
requires an actual integer amount equal to the approved total; local integration
tests cover missing, non-integer, underpaid and correct values. Currency and
provider payment identity belong to the wider contract in 3.7; this gate does
not complete 1.1.
### `[~]` 2.15 — Public intake controls need operational proof
Unfinished reservations now have a one-hour lease and owner-scoped cancellation;
anonymous admission capacity still needs a firm bound. Keep ClamAV signatures
current and alert on stale data; scope reverse-proxy IP trust
to the actual hop and verify it through Swarm ingress. These are separate
controls, but all must work before public large-file intake is considered safe.
The production topology has not been verified by the repository review.
---
@@ -372,8 +441,10 @@ what was promised in the meeting and what exists.
### `[?]` 3.3 — The 5 GB problem is unsolved `(F19)`
Transport accepts 5 GiB; `SCAN_MAX_BYTES` / ClamAV `StreamMaxLength` release only
≤ 128 MiB. Files above that are quarantined permanently with no path forward. This
is exactly the risk Jorge raised in the meeting.
≤ 128 MiB. As of 2026-09-23, customer selection and API reservation reject files
above the effective scan limit before transfer, and the Site displays the current
limit. This prevents a doomed upload; it does not deliver the promised 5 GiB path.
This is exactly the risk Jorge raised in the meeting.
**Decide:** raise the scan ceiling with a resource/timeout design, or define an
explicit large-file path (staged scan, sampled scan, operator override with audit).
@@ -384,11 +455,69 @@ explicit large-file path (staged scan, sampled scan, operator override with audi
round-trip per part → ~640 sequential API calls for a 5 GB file, through an nginx
`limit_req` of 20r/s. Add parallelism (4–6 in flight) and batch presigning.
### `[ ]` 3.5 — Payment ordering `(F27)`
### `[~]` 3.5 — Payment ordering `(F27)`
`dev_paid` charges before persisting the order and passes no idempotency key.
Harmless with `FakePayment`; with Mercado Pago that ordering is how you get double
charges. Fix as part of 1.1.
The local fake `pay` call now runs inside the order transaction, and the inbound
webhook records and applies a delivery transactionally. This does not make an
external charge atomic with PostgreSQL: a provider can succeed while the database
write fails, or deliver the approval later. Add a durable payment intent,
provider idempotency key and reconciliation as part of 1.1 and 3.7.
### `[~]` 3.6 — Preserve and bind the order the customer actually reviewed
The browser's width, copies, rotation, mirroring and repetitions are absent from
the API item, so the factory cannot reproduce the priced layout. Removing an
artwork can leave a stale cart item; editing after quote creation can leave the
old quote payable; a new correction can leave an obsolete final active. Persist
a versioned per-file production specification, tie the displayed cart to its
immutable quote, and tie final approval to the latest correction revision.
Cover the real editor-to-quote-to-final journey, not only the pricing table.
**Local progress 2026-09-23:** The Site includes per-upload width, length,
copies, rotation, mirroring, measurement source, and the exact placement of
each copy in production specification v2. The API checks coverage, dimensions,
film bounds, and quote height; commercial review cannot replace the layout.
The order snapshot and downloadable Kanban manifest retain it. Browser quote actions are disabled when
the cart differs, including same-price changes. Editor changes invalidate the
current cart item immediately; a new customer correction deactivates prior
finals. Browser and local API regressions pass. **Still open:** generate and
validate the final print file from the approved source revision, and
make quote/cart continuity work across devices through a server-authoritative
confirmation flow. Current quote binding is a browser guard.
### `[?]` 3.7 — Complete payment state and reconciliation rules
Event-ID deduplication does not establish which provider payment settled which
quote. Define intent creation, provider transaction ID, currency, paid-at time,
pending/rejected/refunded/cancelled states, late approval after quote expiry,
overpayment and provider success followed by database failure. Record refused
paid events for resolution. Decide who reconciles them and when production must
stop or refund. Implement with 1.1 after the checkout/refund policy is approved.
### `[ ]` 3.8 — Collect a deliverable destination before charging freight
The quote has a shipping service and CEP but no recipient, street, number,
city/state or delivery snapshot. Add and validate these fields with 1.2, then
bind the chosen service and final freight amount to the payment intent.
### `[~]` 3.9 — Make artwork quality and geometry evidence explicit
Reject or route for review when PDF page count/geometry, image decoding or DPI
cannot be established. Do not infer pixels from compressed file size, grade a
mixed item from only the readable files, silently shrink oversized artwork, or
allow a displayed DPI rejection to proceed through checkout. Use representative
real artwork in acceptance checks.
**Local progress 2026-09-22:** DPI refusal and warning acknowledgement now gate
the cart and quote API records the acknowledgement. Oversized loose-art width
is rejected in the UI, API production contract, and packer. On 2026-09-23,
unreadable loose images are blocked, mixed analyzed/manual sheets receive no
automatic grade or discount, and rotated DPI uses the pixel dimension that
corresponds to printed width. PDF dimensions now come from the parsed page
model, with page count, crop, rotation, and UserUnit checks; multi-page and
malformed PDFs block quoting. Isolated browser checks cover those cases and
same-origin PDF rendering. Unsupported PDF image operators and representative
print-file evidence still need correction before this item can close.
---
@@ -403,15 +532,22 @@ charges. Fix as part of 1.1.
window of recent finished ones (`BOARD_FINISHED_LIMIT`, default 50) and the true
finished total. An operator can never lose a card they could act on; only terminal
ones are trimmed. The Kanban column reads "Finalizado · 50 de 213" when truncated,
so the count is not mistaken for an all-time total. Pending quotes are capped too.
so the count is not mistaken for an all-time total. Pending quotes now have
a paginated view; older completed orders still need search in 4.6.
- `[ ]` 4.3 — Scan throughput `(F21)`: one `scan_loop` thread, `worker` at
`replicas: 1`, ClamAV `MaxThreads 2`, browser gives up after 150s.
- `[ ]` 4.4 — Quality grade fallback `(F22)`: when `carregarImagem` fails,
`px(f)=Math.sqrt(f.size/1024)*95` stands — a DPI inferred from **file size in
bytes** — and it drives up to a 25% discount. Fail closed instead.
- `[x]` 4.4 — Failed image decoding no longer infers pixels from compressed
file size. Unreadable loose images cannot enter the cart or receive a grade;
an isolated browser regression covers the failure path (2026-09-23).
- `[x]` 4.5 — Dead config `(F28)`: resolved by deleting `deploy/stack.yaml` in 2.12.
`CLAMD_HOST` no longer appears anywhere; `scanning.py` reaching `'scanner'`
directly is now simply how it works, not a contradiction.
- `[~]` 4.6 — Older unpaid quotes can disappear behind the board limit.
On 2026-09-23 the board began showing newest pending and approved unpaid
quotes separately, with cursor pagination and counts; a local regression
retrieved all 105 pending and 22 approved fixture quotes and cleaned them up.
**Still open:** an explicit terminal state for abandoned/expired quotes and
search/history for older completed orders.
---
@@ -480,6 +616,21 @@ charges. Fix as part of 1.1.
days. The copy now states 30 days, says a later order needs the file again, and
keeps only the true part: order history remains in the account. Policy unchanged;
the promise was corrected to match it.
- `[x]` 5.12 — Repair operational entrypoints after the `local/` split.
On 2026-09-23, staging and both API images package `ops/`; staging calls
`ops.staging_readiness`; local backup calls `ops.storage_backup` through
`compose.local.yaml`; documented security and backup commands use the real
modules. Verified a network-disabled staging pass with non-secret fixture
data, a production API image import, local security status, and a local
backup/restore of the database plus 78 clean objects. Production offsite
recovery and signature freshness remain separate open items.
- `[ ]` 5.13 — Define production recovery: scheduled encrypted offsite database
and object backups, a consistent snapshot boundary, Swarm data placement and
a restore rehearsal that opens every required live order file.
- `[ ]` 5.14 — Promote and verify one immutable release. Scan before publishing
mutable tags, make the source preflight validate the active stack, require the
browser tests to run, test clean install and upgrade, and check application
readiness after Portainer redeploys. Isolate concurrent CI stacks.
---

View File

@@ -77,14 +77,15 @@ the signatures bundled into that image. On closeout it reported ClamAV
below the seven-day alert threshold.
The multipart transport supports uploads up to 5 GiB, but `SCAN_MAX_BYTES` and
ClamAV stream limits release at most 128 MiB by default. Larger files remain
blocked. Supporting larger files requires a deliberate resource/timeout design,
not simply increasing the upload limit.
ClamAV stream limits release at most 128 MiB by default. As of 2026-09-23 the
API and customer picker reject larger files before transfer. Supporting them
requires a deliberate resource/timeout design, not simply increasing the
transport limit.
Run:
```bash
docker compose exec -T api python3 -m app.security_status
docker compose -f compose.local.yaml exec -T api python3 -m ops.security_status
```
An exit status of 1 requires review. At closeout, attention was expected because