CodamAIDocs
Topicdone

From the click to the report

A person has a report produced. Every step through BFF, application, CDMS, CIAS and CRMS to the finished document, embedded and as a separate service, as a preview, final and transient.

Variants
CRMS embedded (local)CRMS as a separate service (remote, crms-client)PREVIEWFINALTRANSIENTrefusal at every station

What this is about

Ben works for the customer nordbau. He has finished an audit and clicks “Create inspection report”. A few seconds later he has a PDF. In between, all three modules work together, and each one does exactly one thing:

  • CDMS holds the data of the audit.
  • CIAS says who is asking, for which tenant and with which roles.
  • CRMS turns a template and data into a document, and keeps it when it is a record.

This page branches off From login to the data. Signing in, BFF and filter chain work exactly as described there. This page is about what comes after.

The whole path

This is the path when CRMS runs embedded in the application and Ben asks for a final report:

sequenceDiagram
    autonumber
    participant U as User
    participant F as BFF
    participant FK as Filter chain (CIAS)
    participant A as custom code of the application
    participant C as CDMS
    participant R as CRMS
    participant DB as Tenant DB
    participant S as File storage
    U->>F: clicks "Create inspection report"
    F->>FK: POST /api/rest/audit/report with bearer token
    FK->>FK: check token, tenant nordbau, gate, roles
    FK->>A: RequestContext filled
    A->>C: reads audit, questions, answers
    C->>DB: SELECT (database of nordbau)
    DB-->>C: rows
    C-->>A: objects
    A->>A: builds the payload, at most two levels
    A->>R: ReportRenderPort.render(FINAL, audit-report, audit-4711, payload)
    R->>FK: who is calling? (from the RequestContext)
    FK-->>R: Ben, nordbau, roles
    R->>DB: looks for the template (Ben, nordbau, system)
    R->>R: checks the payload
    R->>R: renders in the pool, seals the PDF
    R->>S: stores the encrypted bytes, locked
    R->>DB: appends the revision to the chain, journal
    R-->>A: RenderedDocument with revision
    A-->>F: PDF
    F-->>U: download

The following stages walk through the path slowly.

Stage 1: up to the application

As always, the browser calls a route of its BFF. The BFF sends the request with Ben’s access token to the backend. There it first runs through the filter chain of CIAS: check the token, determine the tenant, ask the tenant gate, build the effective roles. At the end there is the RequestContext, the note for exactly this request.

This is the same path as for every other request, station by station on From login to the data. If nordbau is not served, the report ends here with 403, before a single line of the application’s code runs.

Stage 2: the application collects the data

The generator does not create a route for “Create inspection report”. That is custom code of the application: a controller of its own, like the one the hub’s reports have, or a hook. Both are Spring beans, and both can have the CRMS port injected.

What the custom code does
  1. 1
    Application→CDMS
    reads the audit with everything the report shows: lists, groups, questions, answers
    This runs in the RequestContext of Ben's request. That is why CDMS picks the database of nordbau.
  2. 2
    Application
    builds the payload from it: at most two levels, with only text, number, boolean or date at the ends
    Sums, percentages and ratings are calculated by the application beforehand. CRMS does not calculate and loads nothing.
  3. 3
    Application→CRMS
    calls ReportRenderPort.render(…) with template key, format, mode and, for FINAL, a reference
    Result: From here on CRMS takes over. The application waits for the document in the same call.

This is what the call looks like:

RenderedDocument document = renderer.render(RenderRequest.finalDocument(
    new TemplateKey("audit-report"),       // template key
    OutputFormat.PDF,
    new DocumentReference("audit-4711"),   // the chain: the audit's ID
    payload));                             // at most two levels

The reference is the ID of the business object in your application, here the ID of the audit. CRMS does not look into it; it files all final versions of the same report under it. What the payload may look like is on The payload and its check, all fields of the request on The render request.

Stage 3: CRMS asks CIAS who is calling

CRMS does not read a token. It asks CIAS for the caller, and CIAS answers from the RequestContext the filter chain has already filled:

What CRMS getsWhere fromWhat CRMS needs it for
signed in, yes or noRequestContextno person, no report: crms-authentication-required
the personthe user ID from the tokenis recorded on every final document, finds Ben’s personal templates
the tenantthe tenant of the request, here nordbauwhich tenant templates apply, who owns the document
the rolesrealm roles and the client’s roleswriting templates, approving, reading documents

Rendering needs no role. Every signed-in person may render, with every template they can see. Roles only matter when templates are edited and when stored documents are read, see The roles.

Stage 4: finding the template

The request only names the key audit-report. Which edition and which revision renders is decided by CRMS. An edition is the version of a template for one scope, that is for the system, a tenant or a person. The most specific one wins:

Where CRMS looks for audit-report
  1. 1
    CRMS→Database
    Does Ben have an edition of his own (scope USER)? It looks in the database of nordbau
  2. 2
    CRMS→Database
    Does nordbau have an edition (scope TENANT)? Also in the database of nordbau
  3. 3
    CRMS→Database
    Is there a system template (scope SYSTEM)? It looks in the system database
  4. 4
    CRMS
    takes the first edition that has a published revision in a matching language
    Result: If there is none: template-not-found. Ben never finds a template of another tenant.

Embedded, the CRMS tables sit next to those of the application, in the same databases. Which language applies and how CRMS falls back is on How CRMS finds the template and Language and time zone.

Stage 5: check, render, answer

render(FINAL, audit-report, audit-4711, …) in CRMS
  1. CRMS
    Format
    Does the installation offer PDF, and is the format module there?
    ↳ no format-not-offered|pdf
  2. CRMS
    Mode
    Does the installation store final documents?
    ↳ no document-revisions-disabled
  3. CIAS
    Caller
    Is there a signed-in person?
    ↳ no crms-authentication-required
  4. CRMS
    Template
    Does Ben find a published revision of audit-report?
    ↳ no template-not-found
  5. CRMS
    Output type
    Does PDF fit the output type of the template?
    ↳ no format-not-for-template|pdf|<output-type>
  6. CRMS
    Payload
    Does the payload keep the structure rules and the template's fields?
    ↳ no payload-rejected with all violations
  7. CRMS
    Rendering
    Does the template run through in the pool, within the time and size limits?
    ↳ no render-timeout, render-output-too-large, render-capacity-exhausted …
  8. The document is ready. For FINAL it is stored now

Up to the payload everything runs on the request’s thread, because only there is the RequestContext and with it the right database. The rendering itself runs in a bounded pool, see Execution and limits.

For FINAL the step that makes the report a record comes next:

Storing a final document
  1. 1
    CRMS
    seals the PDF, if the installation seals
  2. 2
    CRMS
    creates a key just for this document and encrypts the bytes with it
  3. 3
    CRMS→File storage
    stores the bytes on WORM storage, locked until the retention ends
    WORM means "write once, read many": an object can be neither changed nor deleted until its date.
  4. 4
    CRMS→Database
    appends the revision to the chain of audit-report + audit-4711, with Ben's ID, time, hashes and retainUntil, and writes document-stored to the journal
    The document belongs to the tenant of the request and lives in its database. Without a tenant it lives in the system database.
  5. 5
    CRMS→Application
    returns the bytes, together with the new revision
    Result: The bytes only come once they are stored. There is no final document that Ben has and CRMS does not.

What a revision records exactly is on What a final document is, how the chain exposes changes on The two hash chains.

Stage 6: back to Ben

The application hands the document to the BFF, the BFF to the browser. If something goes wrong, CRMS throws a CrmsException with a key, never a sentence. The application passes the key on unchanged and does not translate it. The frontend shows it in Ben’s language. The hub does it like this: it turns every CrmsException into its own error shape, with the same key and the status CRMS gives that key. The keys are on The error answer.

Embedded or as a separate service

Where CRMS runs, the hub derives from the runtime of the CRMS module, see Embedded or as a separate service. Your code calls the same port in both cases and gets the same objects and keys back. But different things happen on the way.

The same report, embedded and as a separate service

When: The CRMS module sits in the runtime of the application (codamai.crms.placement=local).

sequenceDiagram
    autonumber
    participant A as Application
    participant FK as Filter chain (CIAS)
    participant R as CRMS (in the process)
    participant DB as Tenant DB
    A->>R: render(…) as a method call
    R->>FK: who is calling?
    FK-->>R: Ben, nordbau, roles (from the RequestContext)
    R->>DB: find template, for FINAL write the chain
    R-->>A: RenderedDocument

Result: No network, no second token. Templates and documents live in the application's databases, a FINAL is written in the request's transaction.

When: The CRMS module sits in another runtime (codamai.crms.placement=remote).

sequenceDiagram
    autonumber
    participant A as Application
    participant CL as crms-client
    participant FK as Filter chain in the CRMS service
    participant K as Keycloak
    participant R as CRMS service
    participant DB as DB of the CRMS service
    A->>CL: render(…)
    CL->>CL: Ben's token from the running request
    CL->>FK: POST /crms/render, Authorization: Bearer (Ben's token)
    FK->>K: exchange the token for the service's client
    K-->>FK: token with the CRMS roles
    FK->>FK: tenant from the token, gate, roles
    FK->>R: RequestContext filled
    R->>DB: find template, for FINAL write the chain
    R-->>CL: bytes and Crms-* headers
    CL-->>A: the same RenderedDocument

Result: The person travels along: the call carries Ben's token, never a service token. The service has its own filter chain, its own tenant gate and its own database.

Three consequences of the separate service:

  • The roles come from the service’s client. The service exchanges Ben’s token for one for its own client. So Ben needs a document role at that client, not at the application’s client. See Token exchange.
  • The tenant comes from the token only. crms-client forwards only the Authorization header, no tenant or user headers. The service determines the tenant as if the request came without these headers.
  • Without a running request there is no token. A scheduled job in the application gets crms-authentication-required, and nothing is sent.

How the client turns answers into exceptions is on The client for the separate service.

Preview, final, transient

The path up to rendering is the same for all three modes. What happens afterwards differs:

What the mode changes at the end of the path
PREVIEWFINALTRANSIENT
What forBen looks at the report before he finishes itthe record, for example the finished inspection reporta working copy to pass on, for example the hub's project documents
Referencenoyes, requiredno
Template fromany scope Ben can seeany scope Ben can seeonly the SYSTEM scope
Storednoyes: encrypted on WORM storage, revision in the chain with Ben's IDno
In the documentvisible watermark PREVIEW, marker crms-previewnothing visible, PDF with seal if one is set upno watermark, marker crms-transient in the metadata
Switched onalwaysas long as the installation stores document revisionsonly if the installation switches it on

A typical flow uses two modes: Ben first clicks “Preview” (PREVIEW), checks the result and then finishes (FINAL). Both calls take exactly the same path and render with the same template; only the second one stores something. All rules of the modes are on Preview, final, transient.

What travels in which call

SectionWhat travelsWho checks it
browser → BFFsession cookiethe BFF
BFF → applicationBen’s access token, plus the ID of the auditthe filter chain of CIAS
application → CDMSnothing new: the same RequestContextCDMS with roles and filters
application → CRMS, embeddedRenderRequest as a Java objectCRMS, with the caller from CIAS
application → CRMS servicePOST /crms/render with Ben’s token and the request as JSONthe filter chain in the CRMS service, then CRMS
CRMS → database and file storageno credentials of the person, but the installation’s connectionsdatabase and storage themselves

Where the path ends when something is missing

Why the report does not arrive
Filter chain lets it throughTemplate foundPayload validRendering succeedsWhat Ben gets
no–––401 or 403 from the filter chain, with cias.authentication.…. CRMS was never asked
yesno––template-not-found, as 404
yesyesno–payload-rejected with violations, as 422. The application built wrong data
yesyesyesnoa render-… key, for example render-timeout as 504
yesyesyesyesthe document, for FINAL also stored

Traps

Where to go next

Search