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.

URLhttps://docgen.progressivesurface.com
RepositoryProgressiveSurface/psi-docgen
App Serviceps-docgen (PS-WEBAPPS, shared plan asp-erp-migration-tool, .NET 8)
Private endpointps-docgen-pe10.160.140.25 (PS-ProdData), publicNetworkAccess=Disabled
DatabasePsiDocgen on procserv-proddata (GP_S_Gen5_1 serverless, auto-pause)
Artifactspsdocgenartifacts blob (docgen-artifacts container) — DOCX bytes
Auth profileClient/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)
SecretsNone — fully keyless: SQL via MI (Authentication=Active Directory Default), blob via MI (Docgen:BlobServiceUri + DefaultAzureCredential)
Monitoringps-docgen-insights (codeless agent ~3)
DeployGitHub 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-era catalog_code = WP quote4 code), typed per document type, unit_variant standard|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 real quote.wpd), emits native .docx. Missing blocks flagged inline + X-Missing-Blocks header; 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.Loader CLI): 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 via db/grant-app-mi.sql.
  • Health: GET /health (anonymous 200 by design); everything under /api requires 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 ✓

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.py is 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/html sanitizes server-side (vetted Ganss.Xss + structural backstop) and appends a v2-native version — content_html is the canonical, no Word derivative. DOCX is now a per-version export, hidden for authored versions. Image uploads become first-class block_asset rows.
  • 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}/lineage serves 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 empty requiredResourceAccess: 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 delegated User.Read/openid/profile/email/offline_access plus the app’s own access_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, so aspnet:9.0 under 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 from git log / gh pr list / gh issue list / BUILD_LOG.md rather than read from a doc. CLAUDE.md and README.md lost 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: main has no branch protection and no CODEOWNERS (the three CI checks are advisory); deploy.yml is manual dispatch only — a merge deploys nothing; the CI page-map gate skips with exit 0 when no browser is present.
  • block-a/v2-production branch: its uncommitted Docgen:EnablePdf / Docgen:PdfEngine config-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 of main.
  • 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).