The arc of an engagement
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 in one of two runner containers: runner for data work, runner-ai (the one container holding the AI key and internet access) for anything that calls Claude. Because every piece is a container, the whole stack is portable: it moves to another host without re-architecting. The Runs line under each phase below shows the path that phase takes.
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. How onboarding is built
Onboarding clones the template base (NocoDB's Duplicate Base) into a per-client base: reference data, views, and the empty Client_Profile / CAGE_Codes tables all come along. Every engagement script resolves its tables by title within whichever base it's told to target (NOCODB_BASE_ID), so the same scripts, runner, and n8n buttons drive any client base with no per-client code. When a reference correction lands (a NIST fix, a reworded control), push_reference_data.py propagates it out to every client base: matching rows by business key, dry-run first, and skipping-and-flagging any base that's drifted rather than overwriting it.
Declare the scope up front; every later step keys off that declaration. How the scaffold is built
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). The script is idempotent: linking another framework later only adds what's missing, so re-running is always safe.
One long conversation, recorded in five passes, where each pass feeds the next.
Needs Review down to zero. Clearing Needs Review draws the assessment boundary. How the scoping engine worksClaimed Status: do you actually do this? How triage worksThe client has already said the control is a gap. Route the control to remediation and never request evidence for it.
These are the controls you'll prove. Open an evidence request for each one downstream.
Claimed ≠ assessed. A claim comes before proof; MET / NOT MET is the finding after proof, and the two live in separate fields. A scope-reducing call (CRMA, Out-of-Scope) stays provisional until the control behind the call is proven in Phase 5.
2d · Scoping engine. recompute_scoping_categories.py sweeps the whole inventory and assigns each asset one of the 32 CFR 170.19 categories (CUI Asset · Security Protection · CRMA · Specialized · Out-of-Scope) or Needs Review. The engine is fail-closed: an asset with a missing input can never compute to Out-of-Scope, and lands in Needs Review with a Rationale naming the missing input. 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 existing SSP (extract_ssp_claims.py, where a model reads the plan on the runner with internet access), then check_claim_discrepancies.py flags the telltale mismatch: 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.
The client hands over policies; the automation reads them. How document review works
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 and a section index (pdfplumber / python-docx; no OCR, so a scanned PDF is parked as Cannot Read with an alert). 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, locates every quote in the source itself and derives its citation from that position (a quote not found word-for-word is kept, flagged NOT FOUND IN SOURCE, so invention stays visible; check_quote_fidelity.py re-checks on demand), and writes Evidence as AI-proposed. A parallel job harvests reusable values from the document (the client's name and the like), so later drafts read with their real details instead of placeholders.
Two independent calls on every candidate artifact, worked from a “Review — start here” console and purpose-built action views that surface only the rows and fields each decision needs, with an action-first Suggested action cue on every row saying what to do next. How the evidence queue works
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 by a button at the start of this phase for the controls claimed done in 2e: generate_evidence_requests.py resolves {{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 the artifact against the request's What It Must Show, with three checks:
Verification (have I looked?) and Sufficiency (does it satisfy the objective, or just point at where the answer lives?). Coverage needs both.Accept / Reject on the request triggers reconcile_evidence.py (webhook → n8n → runner, no button to hunt for). The script propagates the decision onto the evidence, rolls the request status up, sends rejected or empty ones back to To Collect (the client sees your Reviewer Feedback beside a rejected request in the portal), and reports how many Perform objectives are now ready to mark MET.Record the findings; the scoring rolls itself up. How marking and scoring work
MET / NOT MET, and record why each NOT MET failed.NOT MET fails the whole requirement; the recompute calculates the DoD SPRS score (points only for what's proven) and opens a POA&M for each new gap the rules allow on one (refusing the rest with the reason), 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 DoD SP 800-171 SPRS score: 110 minus the weight of every requirement not proven MET. Unassessed requirements count against the score, so the number is the defensible floor. A requirement escapes deduction as N/A only when the system is incapable of it, never because the control is simply missing. The recompute writes the score, with the basis for every deduction, onto the assessment and into the SSP. It also auto-creates a POA&M for each newly-failing requirement, linked to the exact objectives that caused it, and never overwrites an existing POA&M, so your remediation notes survive every re-run.
Turn each gap into a specific fix the client can adopt. Every gap is one of two kinds. How recommendations are drafted
Documentation Recommendation: proposed language, placement, based on the standard library.POA&M is recorded automatically, and a proposed remediation is drafted for it.Evidence, which the consultant links back and marks Implemented; the score moves only when Phase 5 marks the objective MET.Documentation side. 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 and propose where it goes. The language reads with the client's real values, not placeholders: {{company_name}} and the like resolve automatically, from values mined out of the client's own documents (and flagged for a human to confirm). 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, and a proposer drafts its fix from the data already linked to the failing requirement: target state from Evidence_Requirements, the function that evidences the objective from the same recipe (Evidence_Requirements.Function), what the client already owns from Client_Technologies, the required value from ODP Values. Each draft is typed: apply a setting to a tool the client owns, or acquire & implement one the client doesn't, with a policy-or-process fallback where no tool function applies. Both sides are one click on the assessment (Propose remediations / Propose documentation). The drafts land in a review queue for the consultant to accept or reject; a draft never marks anything satisfied.
Assemble the artifact that demonstrates readiness. How the deliverables are built
SSP (carrying the SPRS score), the collected Evidence in its own package, and the POA&M as a spreadsheet, one click each, to self-attest with, or hand a C3PAO.SSP: a first-class implementation-statement layer. Nothing is written from scratch. For every objective, how the control is satisfied is authored as its own implementation statement: seeded from the standard library (PPS_Statements) with the client's real values substituted in, and tied to the governing document and the implementing technology. Each statement carries a Draft → Confirmed status, and that Confirmed lock is the gate: the document renders as a DRAFT (“N of M statements confirmed”) until every assessable objective is Confirmed, then it becomes the Final SSP. Completeness is fail-closed as well: unassessed objectives, a MET without evidence, or unsettled scope each mark the consultant's working copy NOT FOR SUBMISSION and block the freeze, so an incomplete assessment can never be archived as a finished one. The SSP exports to Word (.docx) from a one-click Run Action, with the real SPRS score and its basis in the system identification up front.
Package. The client-facing SSP is lean and reads in delivery voice; the evidence and internal workpaper detail split into a companion Objective Evidence Package, with the POA&M as its own spreadsheet. Each is its own one-click export, attached to the assessment, so the set stays traceable to the same data.
Version control. A finalized SSP freezes into an immutable archive: the delivered SSP's text, a SHA-256 hash of it, and a snapshot of scope, score, and frameworks, kept as a version. The assessment is one living record per client scope, so every called milestone (a formal assessment, the annual affirmation, a POA&M closeout) re-freezes the same assessment, and the document-to-document diff is the posture-change record: what moved, what newly passed, what regressed. A called milestone also freezes the evidence behind each MET into the same version: a manifest with the SHA-256 of every artifact, and a package of the SSP, the manifest and the files for the client to keep.
Delivery starts a monthly rhythm. Each month the consultant and the client walk the ConMon calendar together, review the evidence the client refreshed, and work through what changed, so the value of staying ready shows up every month rather than every three years. How Maintain is built
MET never rests on last year's screenshot. The client's new upload goes back through review, and the old one is kept as superseded. The mark and the score stay the consultant's call.MET freeze together, with a SHA-256 hash of each artifact, into a package the client keeps for the six years 32 CFR 170 requires (170.15 and 170.16 for self-assessments, 170.17 and 170.18 for hashed artifacts in certification assessments). Calling a formal assessment moves the engagement into Maintain.Patching, or swapping a tool for a like-for-like one with the same or better security (a FIPS 140-2 firewall replaced by a FIPS 140-3 one). Update the records; no reassessment.
A requirement assessed as N/A now applies (adding WiFi brings the wireless requirements into play). That requirement was never assessed, so the change needs a reassessment.
In between. A change that brings systems, configurations or security tools never assessed before needs careful evaluation with the Affirming Official. Source: DoD CIO CMMC FAQ C-A12, which is guidance, not regulation.
Built. Each evidence requirement carries a freshness window (30 to 365 days) that is copied onto every request, the vision check flags an artifact older than its window at grading time, and a daily sweep ages every accepted artifact against its window (Refresh due, then Stale) across all client bases. Each client's defined frequencies are data, harvested from their documents and confirmed by a person, and a daily ConMon calendar schedules from the confirmed ones. Compliance dates (closeout countdown, affirmation, reassessment) are tracked from what the consultant records. The recompute writes the 180-day closeout deadline into the score's basis and appends a snapshot to Score_History on every run. Daily watches cover the official sources, cloud providers' FedRAMP claims (checked against the GSA Marketplace data) and the DISA STIG library, and each change is routed to the clients it touches for the consultant to review. A monthly pack is built in one click; a proposed technology is reviewed in one click; a called milestone freezes the SSP and the evidence behind it with a SHA-256 hash of each artifact.
Planned. An automated significant-change check: on an accepted environment change, re-run scoping, test each not-applicable requirement for new applicability and draft the note for the Affirming Official.
The loops. New evidence from an adopted recommendation flows back to Phase 4 (re-verify) and Phase 5 (re-score). In Phase 8, evidence that goes stale reopens its request in Phase 4, and a change in the environment reopens scoping in Phase 2. 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. After delivery, Phase 8 keeps that posture current until the next assessment.
CMMC / NIST SP 800-171 Rev 2 readiness methodology · Updated 30 September 2026