CMMC readiness, end to end
Theme
Phase 2 drill-down: 2c data flows and 2d compute the scope

Data flow mapping

How the pipeline is built: interview answers go in as hops, a scoping engine turns them into asset categories, and a generator draws the diagrams straight into the System Security Plan.

12
hops across 5 flows in the synthetic end-to-end run
8 of 12
assets refused a category on the first pass, every refusal correct
1
field flip recomputes the whole inventory, exactly once
0
hand-drawn diagrams: every figure is generated from the records

The whole pipeline

Two jobs share one per-client NocoDB base. Nothing runs on a laptop.

Call Write-back to the base

Both jobs run on the isolated runner container on the VPS, which has no internet egress; neither calls an AI model. Every stage is re-runnable, and every computed field is overwritten on the next run.

1

Capture: the data model

One row per hop, pointing at real inventory rows.

Field names as they appear in the base. Blue fields are written by the scoping engine and overwritten on every run; hand edits do not survive.

Why it is shaped this way

How the interview fills it

  1. Name the flow by the business event, in the "Interview: CUI data flows" view (grouped by flow, ordered by step).
  2. Walk it forward one hop at a time, setting data type, verb, source and destination on each row.
  3. Log the boundary crossings explicitly: the first hop in and the last hop out.
  4. Walk it backward. Backups, logging, monitoring and archives are the hops people forget, and they are the ones that change scope.

An example flow, as rows

StepData TypeActionSource TechnologyDestination TechnologyExternal Endpoint
10CUITransmitEngineering workstationPrime's supplier portal
20CUIStoreEngineering workstationFile server
30CUIProcessFile serverCAM workstation
40CUITransmitCAM workstationCNC machine controller
50CUIStoreFile serverCloud backup service
60SPDTransmitFile serverLog monitoring (SIEM)

Flow Name on every row: "Drawing package from prime to shop floor". Illustrative, not a client. The shaded rows came from the backward walk. Step 40 is CUI because the toolpath is built from the controlled drawing, the same call the synthetic run made.

2

Compute: the scoping engine

Flows plus asset attributes in, one category and a written reason out.

The trigger

  1. The consultant sets Run Action to "Recompute scoping" on any Client_Technologies row.
  2. n8n (run_scoping_recompute_action) passes the row to the runner, which clears the field before the job runs and confirms the clear by reading it back. The sweep writes back to the same table, so every row it touches re-fires the hook; clearing first means those echoes arrive blank and stop at the guard. Measured: one flip, one recompute-scoping call.
  3. The runner sweeps the whole inventory, not the one row. Flipping several rows still produces a single run.

Which input decides which category

CategoryRead fromFrom the flows?
Specialized AssetSpecialized Asset Type (multi-select; "Not Specialized" is explicit, so blank means unreviewed)No
CUI AssetAny Data_Flows hop with Data Type CUI touching the asset, or CUI Handling Intent = IntendedYes
Security Protection AssetSSP-Named For Requirements, used as the observable stand-in for the security job the asset doesNo
Contractor Risk ManagedCUI Handling Intent = Capable but not intended, and a linked Restricting PolicyNo
Out-of-ScopeIntent = Not capable, no CUI hops, no security role, and a Separation Claim backed by Separation Enforced ByPartly

The original plan was to derive every category from flows. Checking the definitions showed flows settle only two of the five, so the other inputs were added to Client_Technologies the same day (2026-07-20).

Precedence, as the engine runs it

  1. Specialized Asset, and nothing else. It is tested first and never combines with another category. If the SSP relies on it for a security requirement, that is a design conflict and goes to Needs Review.
  2. CUI Asset. An asset that handles CUI and also does a security job stays a CUI Asset, the heavier assessment. Its security role is kept in the separate Security Role field rather than lost (since 2026-09-27).
  3. Security Protection Asset. Named in the SSP for at least one requirement. A product-category guess ("firewalls are security tools") only produces a "Likely" prompt, never the category.
  4. Contractor Risk Managed Asset. Only with a linked policy. An unbacked "we don't put CUI there" is not accepted.
  5. Out-of-Scope. The hardest to earn: every condition must be recorded affirmatively.
  6. Needs Review. Everything else. The rationale names the missing input.
Fail closed. An empty row can never compute to Out-of-Scope. Missing or contradictory data lands in Needs Review, because a system quietly excluded from an assessment is the expensive way to be wrong. The consultant works the "Scoping: Needs Review (resolve)" view down to zero, and that is what draws the boundary.

Scope reductions stay provisional

Contractor Risk Managed and Out-of-Scope both rest on a control actually working. The engine reads that control's status from the bound gap assessment and moves the asset accordingly.

Conditioning controlContractor Risk ManagedOut-of-Scope
METStands, confirmedStands, confirmed
Not yet assessedStands, marked provisionalStands, marked provisional
NOT METBecomes CUI AssetBack to Needs Review

The heavier categories are never reduced by this. Only confirmation or failure of a reducing claim moves a row.

What the output reads like

Every row gets a Scoping Rationale sentence. In the synthetic run the engine caught two classic self-reports, a CNC controller and an office multifunction printer, with:

marked 'capable but not intended', yet CUI actually flows to/from it.
If the flow is real it is a CUI Asset, not a Contractor Risk Managed Asset.

It also caught two of the operator's own entry errors: the file server and CAM workstation marked "Logically separated" while CUI flowed straight to them. The run settled at CUI Asset 5, Security Protection Asset 4, Specialized 1, Out-of-Scope 2, Needs Review 0.

3

Render: generated diagrams

The SSP's figures are built from the same rows, so they can't drift from them.

Three generators, one data pull

FunctionDrawsReads
flows_dot()One data-flow diagram per Flow Name, plus one legend for the sectionHops, and each asset's Scoping Category for node shading
network_dot()The boundary: trust zones by category inside a System Boundary box, the perimeter firewall, the internetCategories, plus SSP naming for 3.13.1 / 3.13.5 to find the boundary device
ov1_dot()One-page overview: the enclave, in-scope cloud services inside it, outside parties around itOnly hops that cross the boundary; each party's role is inferred from direction and data type

What flows_dot() emits for the example

// abridged; ids are row ids, fills are the generator's category colors
digraph flows {
  rankdir=LR; bgcolor="white";
  node [fontname="Helvetica", fontsize=10, shape=box];
  extPrimessupplierportal [label="Prime's supplier portal", style="dashed"];
  ct21 [label="Engineering workstation", style="filled,rounded", fillcolor="#f6d0d0"];
  ct22 [label="File server",             style="filled,rounded", fillcolor="#f6d0d0"];
  ct27 [label="Log monitoring (SIEM)",   style="filled,rounded", fillcolor="#cfe2f3"];
  extPrimessupplierportal -> ct21 [label="CUI Transmit", color="#b22222"];
  ct21 -> ct22 [label="CUI Store",    color="#b22222"];
  ct22 -> ct27 [label="SPD Transmit", color="#d2691e"];
  ...
}

From dot to Word

  1. generate_ssp.py writes the SSP as Markdown, with each diagram as a fenced dot block.
  2. export_ssp_docx.py finds each block, pipes it to dot -Tpng, and swaps in an image reference. If Graphviz is missing, the block is left as text rather than failing the export.
  3. pandoc converts GitHub Markdown to .docx against the house reference document.
  4. The file is attached to the Gap_Assessments row through the service account. Two jobs: export-ssp-client (clean) and export-ssp-working (adds proposed text for internal review).

The example, drawn

CUI hop SPD hop Dashed box: outside party CUI Asset Security Protection Specialized

Redrawn in the Direction B palette for this page. The generator still uses its original colors.

Decision log

The calls that shaped the build, and when they were made.

DecisionWhyWhen
Model hops, not flowsDiagrams need edges and order2026-07-20
Use Process / Store / TransmitMatch the definitions the engine implements2026-07-20
Hybrid derivationFlows settle only CUI Asset and part of Out-of-Scope2026-07-20
Clear the trigger before the runStops the write-back from re-firing the hook (moved from n8n into the runner, with a read-back check, on 2026-09-17)2026-07-20
Separation claim plus enforcing controlOut-of-Scope needs separation, and separation needs proof2026-07-22
Two-state coupling to the gap assessmentScope reductions must survive a NOT MET2026-07-22
Generate the diagrams, one per flowA combined graph was unreadable; hand drawings drift2026-07-27
SSP naming is the proxy, not the testSecurity Protection status is about function; the SSP records it2026-09-12
CUI Asset outranks Security Protection; add Security RoleKeep the heavier assessment without losing the security role2026-09-27
Specialized is one category onlyAn asset accepted as not fully securable can't be what the boundary relies on2026-09-29

Known gaps