PSI Docgen
Document-generation platform: content-block store + quote-line-driven DOCX assembly. Replaces the WordPerfect master/subdocument system for proposals, and (next verticals) machine manuals and startup/commissioning checklists.
| URL | https://docgen.progressivesurface.com |
| Repository | ProgressiveSurface/psi-docgen |
| App Service | ps-docgen (PS-WEBAPPS, shared plan asp-erp-migration-tool, .NET 8) |
| Private endpoint | ps-docgen-pe → 10.160.140.25 (PS-ProdData), publicNetworkAccess=Disabled |
| Database | PsiDocgen on procserv-proddata (GP_S_Gen5_1 serverless, auto-pause) |
| Artifacts | psdocgenartifacts blob (docgen-artifacts container) — DOCX bytes |
| Auth profile | Client/app-layer MSAL (auto-redirect); API = AAD JWT bearer. App reg PSI Docgen 8664440d-586c-40d3-8cab-5982faa5c418 is both the SPA client and the API resource, scope api://8664440d-586c-40d3-8cab-5982faa5c418/access_as_user. Tenant-wide admin consent covers Graph User.Read/openid/profile/email/offline_access + the app’s own access_as_user — PSI has user consent disabled, so any new scope needs an admin grant or every login breaks (see Status 2026-07-24) |
| Secrets | None — fully keyless: SQL via MI (Authentication=Active Directory Default), blob via MI (Docgen:BlobServiceUri + DefaultAzureCredential) |
| Monitoring | ps-docgen-insights (codeless agent ~3) |
| Deploy | GitHub Actions deploy.yml, self-hosted runner, az login --identity, async + Kudu poll, health gate 200/401/403 |
What it does
- Content-block store: reusable DOCX fragments keyed by
part_key(dual AFTEC/BC crosswalk + migration-eracatalog_code= WP quote4 code), typed per document type,unit_variantstandard|metric, append-only versioning. - Assembly: walks a quote’s line list (LineType semantics from the live data), resolves each item’s block from the store, merges
@@field@@/{field}tokens into the branded shell (converted from the realquote.wpd), emits native.docx. Missing blocks flagged inline +X-Missing-Blocksheader; free-text lines render as paragraphs; duplicate codes render per occurrence; internal ProposalBOM notes are not rendered (kept in the generation snapshot). - Immutable generation snapshots: line list + block-version set + output blob per generation.
- Migration loader (
PsiDocgen.LoaderCLI): idempotent ingest of converted WPD fragments (manifest + DOCX) with per-folder corpus reconciliation.
Operations
- Schema is admin-applied:
db/apply-schema.ps1(AAD admin token). App MI (ps-docgen) has datareader/datawriter only; MI grants viadb/grant-app-mi.sql. - Health:
GET /health(anonymous 200 by design); everything under/apirequires a token (anonymous → 401 is correct). - SQL DB auto-pauses at 60 min idle — first request after idle takes ~30–60s.
Posture summary (compliance)
Identity-based deploy ✓ · basic creds disabled ✓ · PE + public access disabled ✓ · two-zone privatelink DNS (Azure + DC) ✓ · asuid TXT + A + custom domain + wildcard cert + httpsOnly ✓ · zero plain-text secrets (keyless) ✓ · approved auth profile (app-layer MSAL) ✓ · App Insights ✓ · health gate per posture ✓
Related
deploy-to-azure · webapp-compliance-standard · dns-standards · Plan: docs/superpowers/plans/2026-07-06-proposal-docgen-plan.md (C:\git)
Status (2026-07-13)
- PCLM (Product Content Lifecycle Management) adopted as the platform identity; execution plan: docs/superpowers/plans/2026-07-13-pclm-execution-plan.md (C:/git multi-repo root).
- v2 semantic architecture live in code: blocks carry sanitized semantic HTML + first-class assets (full corpus converted: 6,085 blocks, 2,617 assets); one PSI stylesheet renders DOCX (native OOXML renderer), PDF (print-CSS/Chromium; production = containerized app, decided), HTML.
format=param + capabilities endpoint + UI picker shipped. - Structural-fidelity bar MET: Q9509B vs the 70-page WP original — +2.9% page count, 91.3% page alignment, 100% section order (
scripts/page_map.pyis the permanent gate). - Closed loop live: generation lifecycle (drafted → edited → sent), draft-vs-final deltas, per-block edit-rate (content-debt) analytics; self-contained on-prem watcher (raw .SUM parsing + finals capture) built — deployment to PS-GR-PLCRunner pending.
- Known gaps: p810052/p810056 content recovery; BC quote-line taxonomy decision (ERP workstream); UniData API 70 for the manual family + genealogy.
- Next:
container cutover (unlocks live PDF)(superseded — landed 2026-07-28, see Status 2026-08-28) · UniData service-auth workstream · K:\product corpus migration · Block C (template-JSON, MCP, agent test).
Status (2026-07-16)
- In-browser WYSIWYG editing shipped (replaces Edit-in-Word): TipTap editor constrained to the sanitizer grammar;
POST /api/blocks/{id}/versions/htmlsanitizes server-side (vetted Ganss.Xss + structural backstop) and appends a v2-native version —content_htmlis the canonical, no Word derivative. DOCX is now a per-version export, hidden for authored versions. Image uploads become first-classblock_assetrows. - Corpus grouped by product class: the Product family shows the Document-Manager tree (class → product/phys leaves, content-coverage counts, Unclassified bucket) fed by PSI.UniData.API’s product-class endpoints; falls back to the flat list while that API deploy is pending.
- Spare Parts manual chapter live (first manual-family document type):
GET /api/generate/by-job/{job}?type=spare-parts+ Generate-page “Manual chapter” mode; live AFTEC pull pending the UniData service-auth workstream.
Status (2026-07-20)
- Genealogy live (first phase of the lineage layer): the 2,510 “Metric” blocks turned out to be WP paragraph-metadata cards (not metric mirrors — resolvers now serve Standard only). All 2,500 parseable cards are ingested into
paragraph_card: product class, author, dates, estimator matrix, and 332 copied-from edges resolved to real part keys. The corpus detail shows a Provenance panel (copied-from/copied-by navigation, engineering notes, embedded 1992-96 original paragraphs);GET /api/blocks/{id}/lineageserves the chain.
Status (2026-07-24)
- Sign-in fixed — app registration had no declared permissions. Every login was dead-ending in the tenant’s admin-approval flow because app reg
8664440d-…had an emptyrequiredResourceAccess: nothing was declared, so nothing could be consented. PSI has user consent disabled tenant-wide, so every scope MSAL asks for — the OIDC ones included — needs an admin grant. Declared Graph delegatedUser.Read/openid/profile/email/offline_accessplus the app’s ownaccess_as_user(Docgen is both the SPA client and the API resource, so the self-scope must be declared to be consentable — that was the piece actually blocking token issuance), then granted tenant-wide admin consent. Verified end-to-end in the browser: login → dashboard with live data (6,086 blocks, 200 quotes ready, 15 documents generated), signed in as an account with no per-user consent grant. See webapp-compliance-standard.
Status (2026-08-28)
- Container mode since 2026-07-28 (ACR pull via site MI +
acrUseManagedIdentityCredentials; health gate asserts the pushed image tag, settles 60s, requires 3 consecutive OKs). Live PDF renders in-container via Chromium. - Dependabot guard: major bumps of the Docker base images (
dotnet/sdk,dotnet/aspnet,node) are ignored — CI never builds the Dockerfile, soaspnet:9.0under the net8.0 app passed green and would have failed at container start. Base images move only with the TFM / Node-pin decision. .NET 8 LTS ends 2026-11-10; the move to .NET 10 LTS is psi-docgen #44.
Status (2026-09-09)
- Repo has a state-free
CLAUDE_ONBOARDING.md(PRGJSMES pattern). Cold-start agents read it first; current state is rebuilt fromgit log/gh pr list/gh issue list/BUILD_LOG.mdrather than read from a doc.CLAUDE.mdandREADME.mdlost their dated snapshots (“skeleton, no deploy”, “DOCX is canonical” — the v2 truth is semantic HTML + assets, DOCX/PDF/HTML are renders). - Verified facts the onboarding doc now carries as rules:
mainhas no branch protection and no CODEOWNERS (the three CI checks are advisory);deploy.ymlis manual dispatch only — a merge deploys nothing; the CI page-map gate skips with exit 0 when no browser is present. block-a/v2-productionbranch: its uncommittedDocgen:EnablePdf/Docgen:PdfEngineconfig-gate work-in-progress (a pre-container “prod returns 501 until cutover” gate) is superseded by container mode, where PDF renders in-container unconditionally. Nothing on that branch is ahead ofmain.- The Status entries on this page are a dated history log, not current state.
Created 2026-07-06; updated 2026-09-09 (onboarding doc, block-a WIP superseded).