Phase 2: core domain, policy engine, packet API, Clerical screen

Migration 0003: projects, aliases, renumbers, units, packets and every child table,
change orders, assets, documents, ITPs, station limits, department weld flag.
Policy package: Principal, PacketState, Action rules, custody, preconditions,
CO hold, order of checks, can/blocked/holder on reads. docs/POLICY.md with the
tightenings table for Seth. Write pipeline: FOR UPDATE, CAS on status and dept,
If-Match row versions, server-assigned numbers, field CAS, version bump, packet
log, audit, NOTIFY, signatures, IT reasons, filed lock and HMAC seal.
API: /api/packets (header, transitions, work orders, pages, docs, move, remove,
BOM, shorts, welds, heat tags, heat table, stage, checklist, clock, warehouse
stage, Q.C., change orders, log, seal), /api/projects, /api/units, /api/assets,
/_blob. Frontend: Clerical home, projects and units, packet page with pdf.js
and QR codes, hand draft, heat map, send to warehouse, department or Q.C.
Tests: policy matrix, API, concurrency, schema, projects; Playwright Clerical
draft scenario. Verified on docker-1 with the CI stack and the Samba DC.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-10-16 09:31:45 -06:00
co-authored by Claude Fable 5.1
parent 97b0973130
commit 37d9a0cb50
83 changed files with 16490 additions and 55 deletions
+14 -8
View File
@@ -2,7 +2,7 @@
This document is rewritten for the server build. The old browser build's security document is in the old repo (`C:\Users\spencerm\Coding\PacketStation\docs\SECURITY.md`). The design comes from [BRIEF.md](BRIEF.md) and [PLAN.md](PLAN.md).
Status: phase 1. The identity controls (sessions, PIN, rate limits, audit, Origin check, derived group map) are built and tested against a Samba domain controller in CI. Every other control names the phase that builds it.
Status: phase 2. The identity controls (sessions, PIN, rate limits, audit, Origin check, derived group map) and the policy controls (server policy on every write, workflow steps as compare-and-set, server-side validation and limits, IT edit reasons, the filed lock, the upload pipeline for drawings and documents) are built and tested. Every other control names the phase that builds it.
Contents:
@@ -25,7 +25,7 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
| 7 | Record integrity check | A worker job (seals, asset hashes, audit hash chain) with an alert. | 9 |
| 8 | Self-hosted libraries | Kept: no CDN. The CSP allows nothing external. | 0 |
| 9 | Output escaping | React escapes output by default. The PDF export escapes on the server. | 1, 7 |
| 10 | Central permission policy | Server policy, checked on every write, including writes that do not come from the screens. | 2 |
| 10 | Central permission policy | Server policy, checked on every write, including writes that do not come from the screens. Built. | 2 |
| 11 | PIN sign-off (electronic signature) | Server-verified signature with the PIN or the domain password, stored with who, when, what, factor and a content hash. | 1 |
| 12 | IT edit reasons | Kept. Admin writes need a reason and are marked as IT edits in the packet history and the audit. | 2 |
| 13 | Screen lock | Server-enforced sessions. The browser reports idle and background state; the server records it and refuses an idle or locked session. Unlock is a step-up check on the server. | 1 |
@@ -71,9 +71,15 @@ The M: drive build had 20 layers of protection, most of them enforced in the bro
### Policy (phase 2)
- One policy in Python on the server. Every write goes: session, policy, transition, validation, version check, audit row, in one transaction.
- Every packet read returns `can: [...]` and `blocked: {...}` so the browser enables controls from the server's answer.
- A workflow step taken twice is refused with 409 and the current state.
- One policy in Python on the server (`server/packetstation/policy/`), pure functions with no I/O. Every write goes: session, policy, transition, validation, version check, packet log row, audit row, NOTIFY, in one transaction with the packet row locked. Nothing in the browser decides.
- Every packet read returns `can: [...]`, `blocked: {...}` and `holder`, so the browser enables controls from the server's answer. The server re-checks on the write.
- A workflow step taken twice is refused with 409 and the current state (compare-and-set on the expected status and department). Child rows carry a row version checked with If-Match. Weld, heat tag, page and change order numbers are assigned by the server under the lock.
- Preconditions (heat map answered, drawing attached, BOM pulled or short, checklist complete, welds recorded, Q.C. items complete, closed in VISUAL before filing) run on the server and are never bypassed, Admin included.
- Admin edits outside custody need an IT reason of at least 8 characters, recorded in the packet log and the audit row (`it_reason`). An Admin cannot write a filed packet except through the reopen action, which needs a reason and a signature and breaks the seal.
- Signatures (PIN or domain password, verified in the same transaction) on submit, send to Q.C., Q.C. accept and reject, route, file, reopen, change order issue, approve, reject and verify, the revision approval and hand drawing uploads.
- Input: every string is cleaned (control and invisible characters removed, NFC, trimmed) and cut to its field limit; heat numbers, stamps, project numbers and lots are checked against the old patterns; JSON bodies over 200 KB are refused.
- Uploads: typed by content (PDF, PNG, JPEG), 25 MB limit read in chunks, sha256, stored by id under the assets volume, served only with a session and with `Content-Disposition` and `X-Content-Type-Options: nosniff`.
- The filed lock: a filed packet accepts only the data book date and the Admin reopen. Filing writes an HMAC-SHA256 seal with the server seal key; a reopen keeps the old seal with who broke it and why.
- Details in [POLICY.md](POLICY.md).
### Audit (phase 1)
@@ -122,13 +128,13 @@ The IDs come from Part 3 of the old SECURITY.md. The status follows the approved
| I6 | Largely closed, phase 9 (groups plus reconcile); quarterly review IT's |
| I7 | Closed, phase 1 |
| I8 | App part closed, phase 1; kiosk lock policy IT's (Intune or GPO) |
| A1, A2, A3 | Closed, phase 2 |
| A1, A2, A3 | Closed, phase 2 (server policy on every call, workflow steps as compare-and-set, validation and limits on the server) |
| A4 | Closed, phase 0 |
| D1, D2, D4 | Deferred (envelope encryption, Key Vault) |
| D3 | IT's (host disk encryption on docker-1) |
| D5 | Closed, phase 4 (optional ClamAV) |
| D6, D7 | Deferred (retention period and customer copy need a decision from Seth and Q.C.) |
| M1 | Closed, phase 2 |
| M1 | Closed, phase 2 (append-only audit and packet log written only by the server; every packet write carries both) |
| M2 | Closed, phase 7 |
| M3 | Closed, phase 9 |
| M4, M5 | Deferred (logs to stdout now; SIEM shipping is IT's) |
@@ -136,7 +142,7 @@ The IDs come from Part 3 of the old SECURITY.md. The status follows the approved
| S2 | Closed, phase 1 |
| S3 | Closed, phase 9 (CI scanning) |
| S4 | Deferred to IT (pentest before go-live) |
| S5 | Closed, phase 2 |
| S5 | Closed, phase 2 (React escapes output; server messages are plain text; PDF export escaping comes with phase 7) |
| S6, S7, S8 | Closed, phase 7 |
| O1, O2 | Closed, phase 9 (pg_dump timer plus PBS; one rehearsed restore); quarterly restore IT's |
| O3, O4 | Closed, phase 10 |