Initial Commit
This commit is contained in:
105
openspec/archive/2026-05-25-enterprise-ports/proposal.md
Normal file
105
openspec/archive/2026-05-25-enterprise-ports/proposal.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# OpenSpec Change: enterprise-ports
|
||||
|
||||
- **Change ID**: enterprise-ports
|
||||
- **Package**: `@suaveplan/ports`
|
||||
- **Category / plugin**: `geospatial` / `logistics`
|
||||
- **Target path**: `packages/geospatial/logistics/ports/`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-05-25
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/ports.md`
|
||||
- **Main-branch source**: `/mnt/s/web/suaveplan/packages/universal/ports`
|
||||
|
||||
---
|
||||
|
||||
## Why
|
||||
|
||||
`@suaveplan/ports` is the canonical universal-tier global port database
|
||||
on the main branch. It pairs a curated dataset of sea ports, airports,
|
||||
and rail terminals (UN/LOCODE addressed) with shipping-lane waypoint
|
||||
graphs and ETA estimation. The genesis branch starts empty; landing this
|
||||
package unblocks every logistics/shipping consumer (`port-tracking`,
|
||||
`logistics`, `customs`, `procurement`, `warehouse-ops`, and the matching
|
||||
`api-plugin-*` packages) which all transitively depend on port lookups
|
||||
and lane-based routing.
|
||||
|
||||
The reference implementation on main shipped at 95%+ coverage but
|
||||
without the cross-cutting concerns mandated by the enterprise checklist
|
||||
in `enterprise/ports.md`: stable typed error codes via
|
||||
`@suaveplan/error`, Zod schemas at the public boundary, OpenTelemetry
|
||||
instrumentation, injectable `Clock`, and 100% test coverage. This change
|
||||
rebuilds the package from scratch on genesis with those additions while
|
||||
preserving the full main-branch public surface so direct consumers do
|
||||
not need migration shims.
|
||||
|
||||
## What changes
|
||||
|
||||
### Preserved (from main-branch surface)
|
||||
|
||||
- Curated port dataset split by region: `seaPortsAsia`, `seaPortsEurope`,
|
||||
`seaPortsAmericas`, `seaPortsAfricaMena`, `seaPortsOceania`, aggregated
|
||||
into `seaPorts`; plus `airports` and `railTerminals`. `allPorts`
|
||||
combines every category.
|
||||
- Curated `shippingLanes` waypoint graph spanning the major global routes
|
||||
(Asia–Europe via Suez, Asia–Europe via Cape, Transpacific, Transatlantic,
|
||||
rail and air lanes).
|
||||
- Port lookups: `findPort(locode)`, `findPortsByCountry(country, ports?)`,
|
||||
`findPortsByType(type, ports?)`, `searchPorts(query, ports?)`.
|
||||
- Spatial lookups: `findNearestPort(coord, ports?)`,
|
||||
`findPortsInRadius(coord, radiusKm, ports?)`.
|
||||
- Lane lookups: `getShippingLane(id)`, `getShippingLanesByMode(mode, lanes?)`,
|
||||
`getAllShippingLanes()`.
|
||||
- Lane distance: `estimateLaneDistance(lane)`, `cumulativeDistances(lane)`
|
||||
(nautical miles, great-circle).
|
||||
- Lane progress: `findPositionOnLane(coord, lane)` →
|
||||
`{ progress, remainingDistanceNm, distanceTravelledNm, nearestWaypointIndex }`.
|
||||
- ETA: `estimateETA(currentCoord, lane, avgSpeedKnots?, departureTime?)` →
|
||||
`{ remainingDistanceNm, etaHours, etaDate, confidence }`.
|
||||
- Types: `Port`, `PortType`, `PortFacility`, `LaneMode`, `ShippingLane`,
|
||||
`LaneProgress`, `ETAEstimate`, `NearestPortResult`.
|
||||
|
||||
### Added (genesis enterprise additions)
|
||||
|
||||
1. **Typed error registry** — `PORTS_CODES` via `defineCodes("PORTS",
|
||||
["NO_PORTS", "INVALID_RADIUS", "INSUFFICIENT_WAYPOINTS",
|
||||
"INVALID_SPEED", "MISSING_SPEED", "INVALID_LANE", "PORT_NOT_FOUND",
|
||||
"LANE_NOT_FOUND"])`. Each code surfaces a dedicated
|
||||
`PortsError` subclass extending `SuaveplanError`.
|
||||
2. **Zod schemas** for `Port`, `ShippingLane`, `SphericalCoord`,
|
||||
`LaneProgress`, `ETAEstimate`, `NearestPortResult` exposed under
|
||||
`./schemas/schemas` for boundary validation by API consumers.
|
||||
3. **Injectable `Clock`** — `estimateETA(coord, lane, speed?, opts?)`
|
||||
now accepts `{ departureTime?, clock? }` so callers (jobs,
|
||||
simulation, tests) can supply a deterministic clock. `Date.now()` is
|
||||
never read at module scope.
|
||||
4. **Vessel-class speed defaults** — `VESSEL_CLASS_SPEEDS` constants for
|
||||
`ulcv`, `panamax`, `feeder`, `coastal`, `freighter-air`, `rail-freight`
|
||||
used as fallbacks when neither the lane nor caller specifies a speed.
|
||||
5. **OTel instrumentation** — counters `ports.lookup_total{kind}`,
|
||||
`ports.lane_lookup_total{mode}`, `ports.eta_total{confidence}`; histograms
|
||||
`ports.find_nearest_duration_ms`, `ports.lane_distance_duration_ms`,
|
||||
`ports.eta_duration_ms`. `refreshPortsMeters()` for test isolation.
|
||||
6. **Spatial index** — `PortSpatialIndex` bucketed by 5-degree lat/lng
|
||||
cells, providing O(neighbours) `findNearest`/`findInRadius` instead of
|
||||
scanning all ~120 ports. Used internally by the public functions.
|
||||
7. **Property-based tests** — fast-check invariants for ETA monotonicity
|
||||
(speed↑ ⇒ ETA earlier), distance triangle inequality on great-circle
|
||||
segments, and radius-containment consistency (`findPortsInRadius`
|
||||
never returns a port whose haversine distance exceeds `radiusKm`).
|
||||
|
||||
### Removed
|
||||
|
||||
Nothing. The previous proposal's `PortDatabase` class and JSON-backed
|
||||
"6000-port" dataset are not part of the main-branch surface and were
|
||||
aspirational; this change ships the actual surface main shipped.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected specs**: new `ports` capability spec.
|
||||
- **Affected code**: new package at `packages/geospatial/logistics/ports/`.
|
||||
- **Downstream**: unblocks `port-tracking`, `logistics`, `customs`,
|
||||
`procurement`, `warehouse-ops`, and their `api-plugin-*` companions.
|
||||
- **Manifest**: `MANIFEST.md` regenerated; `STATUS.md` flips `–` → `✅`.
|
||||
- **Category mapping**: adds plugin `logistics` to super-category
|
||||
`geospatial` in `scripts/folder-reorg/assignment.yml` (`logistics` was
|
||||
previously a plugin without a category assignment). The 18 closed
|
||||
super-categories are unchanged.
|
||||
Reference in New Issue
Block a user