# XPRT knowledge graph

XPRT (XML Print Rendering Tool, repo `xprt-app`) is a thin Java service in front of a **Typefi** composition server
(InDesign Server based). Clients send a zipped content package, either ABBI ItemXML or an AD QTI export. XPRT rewrites
the package, picks a Typefi workflow from a mapping hosted on Typefi, starts the job, polls it, and delivers the
finished **print PDF**. ABBI gets the PDF in its S3 bucket and AD gets it through a presigned S3 PUT URL. Staff can also
use it from a small jQuery web page. XPRT does no AI or item generation, and it has no database: all job state is in
memory.

## Start here
1. [[xprt-api]]: the full route table (28 routes), with the auth and callers for each.
2. [[ad]]: the exact AD → XPRT contract (endpoints, multipart fields, JWT, status codes, how the PDF gets back).
   [[abbi]] covers the ABBI v1, v2 and v3 contracts.
3. [[submit-item-for-print]] and [[poll-print-status]]: the two flows that matter.
4. [[job-registry]] and [[job]]: the in-memory lifecycle and dedupe rules ([[job-store]]).
5. [[workflow-selection]] and [[workflow-mapping]]: how a template is chosen (data that lives on Typefi).
6. [[typefi]] and [[typefi-client]]: every outbound Typefi call.
7. [[auth]] and `permissions.md`: what little authentication exists.
8. [[xprt-env]] and [[container-deploy]]: env vars, image and CI.

## Main flows
- [[submit-item-for-print]]: validate → register → parse/JWT → rewrite package → map and stamp workflow → POST to
  Typefi.
- [[poll-print-status]]: Typefi status → log scan on failure → PDF to S3 or presigned URL, once.
- [[cancel-typefi-job]]: UI cancel, plus implicit supersede of v1 duplicates.
- [[view-stats-pages]]: per-day and per-project counts since restart.
- [[pagerduty-health-check]]: eight canary Typefi jobs behind `/api/pagerDuty`.

Capabilities: [[abbi-print-preview]], [[ad-print-pdf]], [[manual-composition]], [[job-monitoring]],
[[usage-stats]], [[health-monitoring]], [[workflow-routing]]. Glossary: [[xprt-name]], [[source]], [[abbi-job-id]],
[[typefi-job-id]], [[pdf-name]], [[form-package]], [[workflow-type]], [[subject-extension]], [[xprt-id-tag]],
[[typefi-job-status]].

## Cross-app
- **AD → XPRT** (AD is the caller). The AD module `ad:xprt-orchestration` (`adam_ad/src/services/xprt_service.go`)
  calls:
  - `POST {XPRT_URL}/xprt/api/item/v1/AD/submit/{jobId}`, multipart with `file`, `jobId`, `uin`, `assetRefId`,
    `creator=AD`, `preSignedURL`, `jwt`, `account`, `bank`, `print_formtype`. Expects 202.
  - `POST …/item/v1/AD/polling/{jobId}`, multipart `jwt`, every 5 s for up to 2 min. 204 means pending,
    `{"status":"200"}` means done, and `xprtError` means failed.

  Auth is an HS256 JWT `{jobId, iat}` signed with AD `XPRT_KEY` and verified with XPRT `xprt_AD_shared_key`. XPRT
  returns the result by PUTting the PDF to AD's presigned `AD_S3_INTERNAL/<entityId>`. XPRT never calls AD's API.
  Details in [[ad]].
- **Abigail** has no direct link. It proxies AD's `/api/data/xprt/*` routes (`abigail:ad-content-api`), and the system
  flow `_system:print-form-via-xprt` covers that path.
- **AD graph correction:** `ad:ad-authoring-ui` calls `XPRTAllowedInteractions` "XPRT (AI generation)". That is wrong.
  The enum is the print-eligible interaction list (Choice, Extended Text, Text Entry, Math Entry), and nothing
  references it.
- **ABBI** (external, no graph): `/item/submit`, `/item/polling`, `/item/v2/*`, `/item/v3/*`.

## Layout
This is a single Maven WAR (`pom.xml`, `<finalName>xprt</finalName>`). It runs on Tomcat 10.1 / JDK 21 and is served
at `/xprt`:
- `src/main/java/com/pearson/xprt/rest/`: seven JAX-RS resources, grouped here as [[api-item]], [[api-jobs]],
  [[api-stats]], [[api-info]] and [[api-pagerduty]], all registered by `XprtApplication` ([[xprt-application]]).
- `models/`: [[job]], `Jobs`, `ItemInputForm`, `Stats`, `TypefiResponse`, `WorkflowMapping`, `ABBIAsset`,
  `Environment`.
- `util/`:
  - Typefi HTTP ([[typefi-client]]);
  - `files/` ([[package-processing]]);
  - `S3/` ([[s3-upload]]);
  - `response/` + `error/` ([[responders]]);
  - `authentication/` ([[auth]]).
- `src/main/webapp/`: the static pages [[index-ui]], [[jobs-ui]], [[typefi-jobs-ui]], [[project-stats-ui]] and
  [[day-stats-ui]].
- Deploy: `Dockerfile`, `conf/`, `envsubst/`, `static-content/`, `app.Jenkinsfile`, `.github/workflows/build.yaml`
  ([[container-deploy]], [[tomcat-config]]).

## Conventions (from AGENTS.md)
- Build and test the way CI does with `mvn clean package`. Run one class with `mvn -Dtest=DirectorySearchTest test`, or
  one method with `-Dtest=Class#method`. Tests are JUnit 4 utility tests only, with no deployed-WAR integration harness.
  Do not delete `src/test/resources/testDir/test-file.xml.new`.
- Build the image only after the WAR exists: `mvn clean package && docker build -t xprt-app:local .`
- Stay on `jakarta.*` APIs and Tomcat 10.1+. Package RESTEasy (core, servlet initializer, Jackson, multipart) in the
  WAR. Keep `web.xml` `/api/*` aligned with `@ApplicationPath("/api")`.
- Config comes from env vars (`Environment` reads `TypefiUrl`, `TypefiUser`, `TypefiPass` from `System.getenv()`).
  Keep `build.number` in `WEB-INF/classes`.
- To add a route, add a method to an existing `Api*` class or register a new class in `XprtApplication.getClasses()`
  and in `XprtApplicationTest`. Every route is `@PermitAll` by default, so add the JWT check deliberately (see the
  `requireJWT` path in [[api-item]]).
- **Do not "normalise" status codes**: ABBI v1 treats 504 as success, several errors are 201, and pending is 200 (v1)
  or 204 (v2+). AD requires a 202 on submit.
- CI: Jenkins (Maven 3.6.3, JDK 11) and GitHub Actions. Images are pushed and deployed only from `master`, `main` or
  `release`, through `xprt-manifest`.

## Known risks
All of these were verified in code at the time of writing. Paths only; no secret values are reproduced.
1. **Most of the API is unauthenticated.** Every resource is `@PermitAll`, there is no `security-constraint`, and
   `SECURITY.md` states that no security is used. Only v3 and AD item routes check a JWT
   (`src/main/java/com/pearson/xprt/util/authentication/JwtDecoder.java`). If the host is reachable, anyone can do the
   following without credentials:
   - cancel **any** Typefi job (`POST /api/typefi/jobCancel`, `rest/ApiTypefi.java`);
   - list 3500 Typefi jobs (`/api/typefi/jobStatus`);
   - empty the job registry (`GET /api/job/truncate`, `rest/ApiJob.java`);
   - silence monitoring (`GET /api/pagerDuty?disable=true`, `rest/ApiPagerDuty.java`);
   - submit compositions through v1 and v2 (`rest/ApiItem.java`).
   An ALB authenticator may protect the UI hostnames (`/api/info/logout` clears `AWSELBAuthSessionCookie*`), but AD
   calls `/xprt/api/item/v1/AD/*` server to server, so at least those paths must be reachable without it. This cannot
   be verified from this repo.
2. **Caller-chosen S3 bucket.** Open v1 and v2 submits take `bucketName` from the request. XPRT later writes the PDF
   there with its own IAM role (`util/S3/BaseManager.java`, `util/S3/FileManager.java`), limited only by what that role
   may write.
3. **Typefi file-system listing proxy.** `GET /api/info/workflowFiles?path=` passes an unvalidated path to Typefi
   `/api/v1/files<path>` with the service account (`rest/ApiInformation.java`, `util/WorkflowRetriever.java`). The
   download routes are limited to `/XPRT/App/*.{pdf,indd,eps,docx}` but are open, and output paths are listed by the
   open `/api/job/jobs`.
4. **JWT weaknesses.**
   - The job is registered **before** the JWT is verified, so a forged request can squat a GUID and cause a 409 for the
     real job (`models/Jobs.java`, `models/Job.java`).
   - Tokens have no `exp`, `aud` or `iss` check and can be replayed within ±300 s.
   - A verification failure logs the **last 6 characters of the secret** (`JwtDecoder.java:57`).
   - On the AD side, `XPRT_KEY` has a committed default (`adam_ad/src/options/options.go`, see the AD graph).
5. **Credential material in the repo.** `static-content/application-users.properties` holds a WildFly-format password
   hash for user `jolokia` (with `application-roles.properties`). The Dockerfile does not copy these files and nothing
   references them, but they are committed. No plaintext Typefi, AWS or JWT secrets are in the repo:
   `config-xprt.properties` holds only a dev Typefi URL, and the template uses `USING_ENVIRONMENT_DIRECTLY`.
6. **Jolokia agent deployed.** `static-content/jolokia-2.6.2.war` is copied to `webapps/jolokia.war` (`Dockerfile`).
   Its `web.xml` requires role `jolokia` with no Tomcat realm (`conf/server.xml`), so it is locked except the anonymous
   `/jolokia/config`. Adding a realm or swapping the WAR would expose JMX over HTTP.
7. **Secrets in logs.**
   - A failed AD upload logs the full decoded presigned URL (`util/S3/PreSignedURLUtil.java`).
   - AD logs the signed JWT at info level (AD side).
8. **Hard-coded hosts.**
   - `rest/ApiItem.java:161` returns a dev Typefi URL in v1 pending responses.
   - `src/main/webapp/scripts/jobs.js` maps only four hostnames and breaks on any other.
   - The Jenkinsfile carries AWS account ids, which are not secret.
9. **Availability.**
   - All jobs and stats live in memory (`models/Jobs.java`, `models/Stats.java`). A deploy breaks every in-flight
     ABBI and AD poll, and multiple replicas need sticky routing.
   - Submits run synchronously with 650 MB unauthenticated uploads.
   - Typefi downloads are buffered fully in memory (`util/TypefiFileRetriever.java`).
   - `Stats` uses unsynchronised `HashMap`s.
10. **Dead or stale code and config.** These look live but are not:
    - `UploadCleanup` cleans the unused `~/upload`, so temp files from error paths are never swept;
    - `DirectorySearch`, `XprtMain` and `src/main/resources/*.typefi_workflow` are unused;
    - `config-xprt.properties` is rendered but never read;
    - there are two identical `TypefiAuthenticator` classes.
