retrospool
your AS/400 reports, as PDFs, over the web
The old world
Business reports on IBM i (née AS/400) land in output queues
(OUTQ), where a Windows box running an ACS printer session has to
"claim" a device and drain them onto paper — or into a folder, if someone rigged it.
The trick: the host queues spool files in *READY whether or not any
printer is alive. The session was never actually required.
retrospool is a self-hosted web application built to replace that whole
ritual: it reads spool bytes directly via
JTOpen — no TN5250E negotiation, no
device-name juggling, no port 992 — and turns them into PDFs. The
capture-and-render core and credentialed operator console are built;
the
queue poller that drains a live OUTQ on a schedule, and export
fan-out to S3 / SFTP / FTPS, are the next phases (see
status).
The pipeline
IBM i OUTQ ──▶ poll (JTOpen) ──▶ sniff format ──▶ split concatenated PCL
[poller: planned] │
┌───────────────────────────────┤
▼ ▼
store original bytes render PCL ─▶ PDF
(.pcl / .pdf / .bin) (GhostPDL sidecar)
│ │
└───────────┬───────────────────┘
▼
tenant-scoped capture (dedup by sha256)
│
▼
export fan-out: S3 / SFTP / FTPS [planned]
The shaded middle band — sniff → split → store → render → capture — is
built and covered by end-to-end tests today. The queue poller that feeds it, and the
export fan-out that drains it, are the next phases.
What works today v0.2.0 · verified
The backend is exercised by unit + Testcontainers integration tests against real
Postgres, MinIO, and the actual gpcl6 sidecar. The production frontend
bundle is browser-driven to generate the product views below, with console and page
errors treated as failures.
- Format sniffingSpool files are
DEVTYPE(*USERASCII)opaque byte streams — PCL, PDF, plain text, or mystery bytes. Detected from the first 16 bytes. - PCL splittingConcatenated reports split on
ESC Eprinter resets, guarded by a preceding form feed (0x0C) to dodge false positives inside binary sections. - Rendering without license riskGhostPDL (
gpcl6) is AGPL, so it runs as an isolated sidecar container behind a tiny HTTP shim — never on the JVM classpath. A GradlelicenseGatetask fails the build if it, or any banned artifact, sneaks in. - Originals are sacredEvery PCL capture keeps both artifacts: the original
.pcl(source of truth, re-renderable) and the rendered.pdf. A render failure never loses a report. - Multi-tenant by constraintOne tenant per company; every query
tenant-scoped; dedup within-tenant only via
unique(tenant_id, sha256, segment). A reflection test gate fails the build if a repository query forgets itstenantId. - Idempotent by constructionRestarts, re-polls, and races all collapse into the same database constraint. Duplicates are skipped, never errors.
- Operator consoleA responsive React admin surface for dashboard counts, submission review, tenant settings, capture downloads, and ephemeral IBM i sign-on tests, guarded by Authentik forward-auth identity.
See it work
These are screenshots of the actual production-built admin console, not hand-drawn mockups. A Playwright harness launches the app and supplies deterministic API fixtures; all company names, hosts, IDs, and report details shown here are invented demo data.
Stack
Spring Boot 3 · Java 21 (Temurin) · JTOpen · PostgreSQL 16 + Flyway · PDFBox · AWS SDK v2 · GhostPDL (sidecar only) · React 18 · Vite · TypeScript · TanStack Query · Tailwind · Testcontainers (real Postgres, MinIO, and the actual gpcl6 sidecar in CI-style integration tests) · Docker end-to-end — the dev machine needs no JDK.
Status
v0.2.0 — pre-1.0 and honest about it. The capture-and-render core, authenticated admin API, React operator console, and the public HOD/WS submission intake are delivered. Live queue polling and export fan-out remain next — both gated on a real IBM i host, which is also what 1.0.0 waits on.
- Connection layer shippedTLS signon via
SecureAS400and a synchronous Test Connection endpoint. The mechanism is wired and unit-tested; a live green signon awaits operator host + credentials (no IBM i emulator exists). - Persistence model shippedFlyway V1+V2,
JPA entities and tenant-scoped repositories, write-only
SecretResolver, and a reflection gate that fails the build on an un-scoped query. - Capture pipeline shippedSniff → split
→ store → PCL→PDF render → dedup, plus S3/MinIO landing storage
— end-to-end tested through the real
gpcl6sidecar. - Queue poller plannedScheduled
*READYpolling per output queue with watermark advance. Gated on a live host to validate against. - Export destinations plannedFan-out to S3 / SFTP / FTPS with retry + backoff. Only the S3 landing store exists today.
- Admin API & React console shippedAuthentik forward-auth identity, dashboard statistics, tenant detail, submission approval, capture downloads, and Test Connection are wired into the production image.
- Public HOD intake plannedThe operator-side
review and promotion gate is present; public ACS
.wsparsing and draft submission remain a later phase.
The full phased plan and an append-only decision log live in docs/.