# Workspace permit route integration

## What is integrated

Every newly inserted permit with a file creates a processing job, including
direct Synchron carrier imports. Native uploads, the carrier wizard, intake uploads and the signed
Synchron callback also try that same processor immediately.

Processing: file → existing GPT extraction/route matcher → save permit extraction
and route token in Supabase → non-dimension warnings → existing all_trips sync.

Workspace dimension-mismatch warnings and emails are temporarily paused. With
`WORKSPACE_DIMENSION_ALERTS_ENABLED` unset or `false`, new mismatches are not
published, old mismatch rows are hidden from trip/dashboard views, and no
dimension email is sent from uploads or edits. Other warning types still work.
The historical rows are retained, not deleted. Re-enable only after client
approval by setting this server variable to `true` and redeploying; review/recheck
historical warnings before doing so because retained rows may be stale. This
setting does not affect GPT's Synchron permit flow.

Credit checks and deductions are intentionally NOT implemented in this phase.
No paid route request is created by this pipeline. Existing unrelated billing
code is unchanged. Imported trip origin remains `trips.source_system=synchron`;
that field is also included in the existing MongoDB workspace snapshot.

GPT's Workspace response contains `states_info[].route_token`, `route_texts`,
`route_status`, `route_source` and any matched catalog `route_links`. Existing
HTTPS Google Maps parts are saved on the permit and displayed as ready; a token
without links remains “map preparation pending.” No route-token lookup or map
generation endpoint is involved, and extraction is not repeated merely because
maps were absent. Future route text is named `routing_from`, `routing_to`, `via`;
historical MongoDB arrays are not migrated.

GPT now selects isolated storage inside its existing Workspace extraction endpoint.
The state matching algorithms are reused. Matching Synchron catalog routes keep
their existing numeric token, without incrementing Synchron's `Frequency`.
New routes use `workspace:<hash>` tokens in `workspace_routing_tokens`; these
tokens are identifiers, not URLs or authentication keys. Usage is recorded once
per permit/file in `workspace_route_usage`. No billing is performed for either
trip source in this phase.

Workspace extraction no longer writes shared `auto_orders`, `auto_carriers`,
`auto_trucks`, `auto_trailers` or `hammer_gpx` placeholders. Its audit remains in
`workspace_permit_extractions`. `all_orders` is read-only when seeding SP trip
data; it is never written by these Workspace paths. Synchron keeps its default
storage and existing API behavior. GPT needs deployment before Workspace.

The mirror matches by Workspace permit ID before any legacy fallback. Repeated
syncs fold duplicate IDs, same-state permits stay separate, and removed permits
leave the active `order.routeData`. Optimistic document revisions prevent a sync
from losing a concurrent extraction/RateCon update. Supabase remains the source
of truth; late extraction for a replaced file is rejected when the replacement
has already reached the GPT snapshot. Supabase's own lease/revision check remains
the final publication guard.

A state without an implemented matcher returns `unsupported` (manual preparation).
A detection/provider/storage failure returns `failed` (retry). A token returns
`needs_preparation`, not `ready`: automatic maps/GPX delivery remains deferred.
No bot/voice changes or new paid route request endpoint are part of this work.

## Manual deployment steps

1. Deploy the GPT changes first. Existing Workspace credentials need
   `extractor:permit.extract` and `agent:order.query` (the current sync scope).
   Workspace calls `POST /api/workspace/extract/permit` and
   `POST /api/workspace/update-trip`. No new endpoint or API secret is needed.
   Its MongoDB user needs read access to the shared route catalog and existing
   source orders, plus write access to the Workspace collections listed above
   and `all_trips`. MongoDB creates the new Workspace collections on first use.
   Ensure all GPT web workers run the new version before bulk processing; do not
   leave old workers serving Workspace extraction during a rolling deployment.
   If using extra `SYNCHRON_PERMIT_FILE_HOSTS`, configure the same host names in
   both repos. Defaults already allow the two existing Synchron permit hosts.
2. Apply Supabase migrations through `0041_permit_processing.sql` in order,
   followed by `0042_operation_events.sql` for the Operations Log.
   Migration `0036_permit_dimension_alert_deliveries.sql` is also required for emails.
   Apply to a staging database and test first; these migrations were not applied
   to live Supabase during implementation.
   The permit-processing and operations-log migrations were renumbered from local
   versions 0037/0038 because the pulled migrations already occupy 0037 through 0040.
   Keep the pulled migrations unchanged. If you already applied either earlier
   local version, reconcile the database's migration history before running the
   renamed files; do not blindly rerun them (permit triggers may already exist).
3. Deploy Workspace. Keep existing HHA, Supabase, S3 and email settings.
   Configure `NEXT_PUBLIC_APP_URL` to the public Workspace origin and
   `CRON_SECRET` (at least 32 characters) on the Workspace server. No new key
   values are checked into Git.
4. Schedule this command every minute from the Workspace project directory
   using the deployment's scheduler, with a Node version supporting `--env-file`:

   ```sh
   node --env-file=.env scripts/permit-processing.mjs --run-one
   ```

   Alternatively, the scheduler can POST `{}` to
   `/api/internal/permit-processing` with `Authorization: Bearer <CRON_SECRET>`.
   Set secrets in the scheduler securely, never in a committed cron file.
   Each invocation handles ONE due job. Allow 300+ seconds runtime. Overlapping
   invocations are leased; the same revision cannot be published twice at once.
   Extraction failures retry with backoff; worker crashes recover after 15 minutes.
5. Queue existing permits only after reviewing a preview (no historical emails):

   ```sh
   node --env-file=.env scripts/permit-processing.mjs --enqueue --trip-id TRIP_UUID
   node --env-file=.env scripts/permit-processing.mjs --enqueue --trip-id TRIP_UUID --apply
   ```

   Replace `TRIP_UUID` with the Supabase UUID. Omit `--trip-id` to cover all
   existing permits. Preview is read-only. `--apply` only inserts missing jobs;
   it neither resets active jobs nor processes files immediately. Processing
   later uses GPT/OCR resources, even though no customer credits are deducted.
   Dimension warnings remain paused for historical and new jobs until enabled.
   Newly imported completed SP trips also suppress historical emails, even if
   a contact has already claimed their account. Live trips only send dimension
   alerts if the Workspace pause is explicitly lifted.

## Checks after deployment

1. Import one SP trip using the existing transfer script. Its permits must appear
   in `permit_processing_jobs`, then get a token/status without any credit usage.
2. Upload a permit on an HHA-created trip (PDF, then PNG/JPEG). Confirm extraction,
   non-dimension warnings and token on Supabase `permits`. Check the existing MongoDB
   `all_trips.workspace_snapshot.permits` mirror as well.
3. Upload a permit whose route matches a Synchron catalog token with saved
   `Route_Links`. Confirm Supabase `permits.route_links` contains the Google Maps
   parts and Workspace shows “Route ready.” A token without links must remain
   pending; existing imported/purchased maps must still open.
4. Stop GPT temporarily in staging. The file/permit must stay saved, with a retry
   job. Restart GPT and invoke the worker; processing should recover.
5. Change a permit file during processing. Only the current revision may publish.
6. With a deliberately smaller permit dimension, confirm no visible dimension
   warning or dimension email while the pause is active. Expiry warnings should
   still display. Do not test real alert delivery until reactivation is approved.
7. Test an existing SP order normally. Its matching rules, callbacks and default
   storage are unchanged. Snapshot a shared route's `Frequency` before and after
   processing a Workspace permit that matches it: Workspace must not increase it.

## Operation and rollback

Inspect `permit_processing_jobs.status`, `last_error`, `attempts` and `available_at`
with read-only queries. Failures log `workspace_permit_processing` with permit ID,
revision and an internal error code; no credentials or raw provider bodies.

No historical queue, live extraction, emails or production DB writes were run
as part of local implementation. Live provider/email/link tests remain deployment
checks. Stop the scheduled worker first if rolling back. Restore the previous
Workspace release; leave additive Supabase columns/table in place so permit data
is not deleted. Do not revert GPT isolation while the Workspace queue is running:
the older GPT release would increment shared route counters again. Pause Workspace
permit submissions too if reverting GPT. Keep new Mongo collections for recovery.

## Local verification (September 28, 2026)

- TypeScript check and targeted ESLint: passed.
- Workspace test suite: 567 passed with `TZ=UTC`.
- GPT coverage exercises all 47 real state matching functions using mocked OCR
  and in-memory database boundaries. It also covers same-state permits, repeated
  sync, concurrent writes, file replacements, image source URLs and API isolation.
  The New York module needed a missing `os` import to load; no state matching
  algorithm was rewritten. See GPT's `WORKSPACE_ROUTE_TOKENS.md` for details.
- GPT final unit suite: 306 passed and the same 18 pre-existing failures as the
  pre-edit baseline. All 135 targeted Workspace integration tests pass.
- No PostgreSQL runtime was available locally. The SQL migration and actual
  trigger/RPC behavior must be exercised in staging before production.
