Commit 3480d88
committed
feat(tms): capability profiles with graduated enforcement and a deviation ledger
Phase 0 of the multi-mode expansion. Introduces the mode abstraction that
later modes (flatbed, bulk/tanker, drayage, final mile) build on, with zero
behavior change for existing organizations.
A "mode" is a preset of capability flags over three orthogonal axes — service
model, equipment class, execution party — rather than an enum on Shipment.
Customers run combinations (dedicated reefer, hazmat bulk, flatbed heavy haul),
so a single mode field would branch combinatorially by the second mode.
Three design commitments:
Graduated enforcement replaces boolean feature flags. Every capability rule
carries tenant.EnforcementLevel (Ignore/Warn/RequireReview/Block) instead of an
on/off switch. Defaults ship at Block so the system drives the process; an
organization dials a specific rule down without forking anything.
Warn and RequireReview results are recorded, not silenced. They become
Deviation records acknowledged with a reason, producing a queryable ledger of
where an organization departs from the standard process and why.
Resolved policy carries provenance. ShipmentUIPolicy now returns which profile
produced each rule, at what enforcement, whether it was overridden and why, plus
the rejected candidate profiles and the reason each lost. The UI can answer
"why is this required" inline instead of leaving users to guess.
Resolution mirrors detention.Policy exactly (priority, specificity score,
effective-date windows) rather than introducing a second algorithm. Capability
rules compose into the existing validationframework as conditioned rules; no new
validation engine. Field descriptors reuse customfield's FieldType,
ValidationRules, and UIAttributes.
errortypes.MultiError gains an Advisories slice, additive so HasErrors() and
every existing validator are untouched.
Enforcement migrated to profile-authoritative: cargo.maxShipmentWeight,
dispatch.moveRemoval, and the new cargo.temperatureRange. Hazmat segregation and
duplicate-BOL are represented in the resolved policy for explainability but keep
ShipmentControl enforcement; they migrate in Phase 1.
Zero behavior change is enforced two ways: when no profile resolves the
validator falls back to ShipmentControl, and the migration backfills one
org-default profile per organization with enforcement derived from that
organization's existing shipment_controls row. The new temperature rule
backfills at Ignore.
Client: capability helpers and the explainer live in packages/shared so both
apps consume them. ShipmentGeneralInformation is the first converted section —
temperature fields now render only when the profile declares TemperatureControl,
so the form gets shorter as modes are added rather than longer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01559YzTFTKd3vV6eJWmSDzi1 parent 4a8620b commit 3480d88
36 files changed
Lines changed: 5191 additions & 102 deletions
File tree
- client
- apps/web/src/routes/shipment/_components
- services/tms
- internal
- bootstrap/modules
- api
- core
- domain/modeprofile
- ports
- repositories
- services
- services
- modeprofileservice
- shipmentservice
- infrastructure/postgres
- migrations
- repositories/modeprofilerepository
- pkg
- buncolgen
- errortypes
- validationframework
Lines changed: 43 additions & 20 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
4 | 11 | | |
5 | 12 | | |
6 | 13 | | |
| |||
23 | 30 | | |
24 | 31 | | |
25 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
26 | 41 | | |
27 | 42 | | |
28 | 43 | | |
29 | 44 | | |
30 | 45 | | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
51 | 74 | | |
52 | 75 | | |
53 | 76 | | |
| |||
Lines changed: 87 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
0 commit comments