How a CMMC 2.0 / NIST SP 800-171 readiness engagement runs. The question isn't just where are my gaps — it's am I ready to self-attest in SPRS, or to pass a C3PAO assessment? Finding gaps is one step toward that answer, not the whole of it.
Every piece — the database (NocoDB), the orchestrator (n8n), the script runners — is a Docker container on a dedicated VPS, never a local computer. An action or upload in NocoDB fires a webhook to n8n, which runs a Python script on the host: runner for data work, runner-ai (the one container holding the AI key and internet access) for anything that calls Claude. Because it's all containers, the whole stack is portable — it can be picked up and rehosted elsewhere without re-architecting. The chips under each phase below show that phase's actual path.
The same process as a decision flow — where a control forks toward evidence or toward remediation, and the loop that turns a gap into coverage. Node colors match the legend below.
Stand up the client's own isolated workspace before any data is entered.
Declare the scope up front — everything downstream keys off it.
Populate objectives → the full control checklist appears in seconds.populate_gap_assessment_objectives.py reads the linked framework(s) and creates a row for every assessment objective (Gap_Assessment_Objectives) and every control (Gap_Assessment_Requirements). It's idempotent — linking another framework later only adds what's missing, so re-running is always safe.
One long conversation, recorded in five passes — each feeds the next.
Needs Review down to zero. This is where the boundary is drawn.Claimed Status: do you actually do this?It's a gap already found. Route to remediation — never request evidence for it.
This is what you'll prove. Open an evidence request for it downstream.
Claimed ≠ assessed. A claim is pre-proof; MET / NOT MET is the post-proof finding — separate fields. And a scope-reducing call (CRMA, Out-of-Scope) stays provisional until its control is proven in Phase 5.
2d · Scoping engine. recompute_scoping_categories.py sweeps the whole inventory and assigns each asset a category per 32 CFR 170.19 — CUI Asset · Security Protection · CRMA · Specialized · Out-of-Scope · Needs Review. It's fail-closed: an asset with a missing input can never compute to Out-of-Scope — it lands in Needs Review with a Rationale naming the gap. Scope-reducing calls (CRMA, Out-of-Scope) stay provisional until their control is proven MET in Phase 5.
2e · Triage. A plain-language Triage_Questions bank drives the "do you do this?" pass. The claim is pre-seeded from the tool stack (infer_claims_from_stack.py) and any SSP (mine_ssp.py), then check_claim_discrepancies.py flags the tell: claimed done, but you run none of the tools this control needs. Every claim carries a provenance rank — Client-confirmed > Interview > SSP > Tech-stack inferred.
Client hands over policies; the machine reads them.
Evidence as verbatim quotes with a sufficiency judgment, marked AI-proposed.Satisfied by documents → the automated review above proposes candidates.
Need a screenshot or config export → personalized evidence request → vision auto-check → reconcile.
Upload fires a webhook → ingest_client_documents.py extracts the text (pdfplumber / python-docx; no OCR, so scanned PDFs come back empty — spot-check). Flip to Queued and review_document.py batches the in-scope Define objectives by control family, asks Claude for verbatim quotes plus a Sufficiency judgment, then mechanically verifies every quote against the source (check_quote_fidelity.py) — a quote not found word-for-word is discarded — and writes Evidence as AI-proposed.
Two independent calls on every candidate artifact.
Verification Status — have I actually looked at this?Sufficiency — does it satisfy the objective, or just point at where the answer lives?Evidence linked to the personalized Evidence_Request generated back in 2e — generate_evidence_requests.py had resolved {{technology}} to the client's actual tool and led each ask with the plain-language Assessor Intent.check_evidence_vision.py sends each artifact to Claude vision and grades it against the request's What It Must Show — three checks:
Verification (have I looked?) and Sufficiency (does it satisfy the objective, or just point at where the answer lives?). Coverage needs both.reconcile_evidence.py rolls the request status up from its evidence, sends rejected or empty ones back to re-request, and reports which Perform objectives are now ready to mark MET.Record the findings; the scoring rolls itself up.
MET / NOT MET — and write down why it failed.NOT MET fails the whole requirement; it scores SPRS and opens a POA&M for each new gap — never touching your existing remediation notes.Marking an objective fires recompute_gap_assessment.py automatically (webhook → n8n → runner). It rolls objectives up to requirements — one confirmed NOT MET fails the requirement, even with others unassessed — computes the SPRS score from the DoD Assessment Methodology point values, and auto-creates a POA&M for each newly-failing requirement, linked to the exact objectives that caused it. It never overwrites an existing POA&M, so your remediation notes survive every re-run.
Turn each gap into a specific, adoptable fix — and every gap is one of two kinds.
Documentation Recommendation: proposed language, placement, based on the standard library.POA&M is recorded automatically, and a proposed remediation is drafted for it.Evidence, linked back and marked Implemented.Documentation side (built). The draft is pulled from the standard library — PPS_Statements, one atomic policy / procedure / standard statement per assessment objective. Pick the statement that satisfies the objective, adapt it (the client's real names substitute in via PPS_Variables), and propose where it goes. Based On links the draft back to its source statement, so every recommendation is traceable to library language rather than free-written, and proposed text is never counted as coverage.
Implementation side. When a technical control isn't in place, the recompute opens a POA&M automatically — but its Remediation Plan currently lands as a blank field a human fills in. The planned proposer reads the same structured data already linked to the failing requirement — target state from Evidence_Requirements, the satisfying tech class from Requirement_Technology_Map, what the client already owns from Client_Technologies, the required value from ODP Values — to draft the remediation and type it: apply a setting · acquire & implement a tool · extend coverage. (This proposer isn't built yet — today the POA&M is raised, then the remediation is written by hand.)
Assemble the artifact that demonstrates readiness.
SSP, the POA&M, the collected Evidence artifacts, and the SPRS score into one deliverable to self-attest with, or hand a C3PAO.SSP. Nothing is written from scratch — the plan is assembled from data already proven in earlier phases: per-control implementation statements from the collected Evidence, narrative from PPS_Statements, and the client's real Client_Technologies and roles. Because it draws only on human-verified rows, the SSP describes the system as it was actually assessed. (The assembly script is still being finished — this is the one phase not yet fully built.)
Package. Bundle the SSP, the POA&M, the verified Evidence artifacts, and the SPRS score into a single export — one traceable deliverable to self-attest with, or to hand a C3PAO.
The loop. New evidence from an adopted recommendation flows back to Phase 4 (re-verify) and Phase 5 (re-score). An unimplemented recommendation stays visibly distinct from a satisfied one — so proposed language can never be mistaken for coverage.
A defensible readiness posture, packaged — a generated SSP, a scored SPRS self-assessment, a POA&M for every remaining gap, and verified evidence behind every MET. Enough to self-attest with confidence, or to hand a C3PAO on day one of a certification assessment.
CMMC 2.0 / NIST SP 800-171 readiness methodology · portable Docker containers on a dedicated VPS.