Secure Software Delivery for Federal Environments Module 2 · Controls Into Code

The Control Catalog as Data

Last reviewed

Intermediate

What you'll learn

~18 min
  • Structure a compliance program as a three-layer system: universal catalog, per-system config, per-run outputs
  • Design a per-control schema whose required fields force assessor-grade answers
  • Explain why an enum for sampling strategy beats a free-text field - and generalize the principle

From binder to repository

Meridian’s vendor seat, week one: the receiving system’s ISSO asks how Meridian will demonstrate its controls. The reflex answer — a spreadsheet with a row per control and a Status column — is where compliance programs go to rot. This module teaches the alternative that the strongest reference programs actually run: the control catalog as a schema-gated data repository, where every downstream artifact (statements, collectors, assessments, exports) is generated or validated from one source of truth.

The architecture that makes it work is a three-layer separation, and the layers earn their boundaries:

LAYER 1: UNIVERSAL CATALOG one file per control - environment-agnostic
(the product) by construction: CI LINT FORBIDS system
identifiers here. This layer is reusable
across every system you will ever authorize.
LAYER 2: PER-SYSTEM CONFIG what THIS system inherits, tailors, scopes -
(the instantiation) the only layer that knows deployment names,
subscriptions, boundaries.
LAYER 3: PER-RUN OUTPUTS dated, immutable results of each collection
(the record) and assessment run (Module 3).

The payoff arrives with the second authorization: it’s a sibling directory in layer 2, not a fork of the whole program. The reference shop that runs this pattern maintains one catalog of 84 technical controls across 10 families serving multiple system packages — and the lint rule keeping system IDs out of layer 1 is what makes that possible. It’s the same discipline as the DevSecOps Foundations training’s template libraries: the reusable thing stays parameterized, and CI enforces the boundary.

One YAML per control — and the fields that do the work

Each control is one file, schema-validated on every merge. The shape (paraphrased teaching version):

# controls/AC-2.yaml - one control, one file, schema-gated
control: AC-2
family: access-control
baseline: moderate # baseline membership is a FIELD, not a folder
componentImplementations: # OSCAL-shaped on purpose (Lesson 2.3)
- component: identity-platform
status: implemented
origination: hybrid # system | inherited | hybrid
roles: [platform-team, isso]
statement: >
(the human-authored implementation narrative - Lesson 2.2)
scope:
boundary: > # REQUIRED: what this control claims to cover.
# Phrased GENERICALLY here (layer 1) - layer 2 binds real names/IDs:
all workforce identities within the authorization boundary;
break-glass accounts excluded per documented exception
sampling:
strategy: full-population # ENUM: full-population | time-window | risk-based
evidence: # the CONTRACT for Module 3's collectors
automated: [identity-export, access-review-log]
operator: [quarterly-review-signoff]
query: [kql/ac-2-stale-accounts.kql]

Three design choices here are the lesson:

scope.boundary is mandatory. The most common assessment failure isn’t a missing control — it’s a control whose claimed coverage was never stated, so the assessor discovers the exclusions live. Forcing the boundary into the schema means every control answers “covering what, exactly?” before any assessor asks.

sampling.strategy is an enum, not a string. Free text invites “we sample appropriately” — the hand-wave that dissolves under 800-53A procedures. An enum forbids the hand-wave: you either assessed the full population, a defined time window, or a documented risk-based subset. There is no fourth value, so there is no vague answer. This is the generalizable principle: wherever assessors probe, replace prose with a closed vocabulary.

evidence is a contract, not a description. Naming the automated feeds, operator artifacts, and queries per control is what lets Module 3’s collectors be driven by the catalog — and what makes a control with no evidence entry visibly unverifiable at merge time instead of at assessment time.

Prompt first: generate the skeleton, argue with the schema

Read our current compliance spreadsheet (attached) and draft the
three-layer skeleton: one YAML per control following the schema in
schema/control.schema.json. For every row where the spreadsheet
cannot fill a REQUIRED field - scope.boundary missing, sampling
unstated, no evidence source named - do NOT invent content: emit the
file with a FIXME block naming who must answer (ISSO vs platform team
vs inherited-from-provider) and what question they must answer.
Then summarize: how many controls arrived complete, and the top five
questions the spreadsheet never answered.

The FIXME discipline matters more than the generation. An AI CLI will cheerfully fabricate a plausible scope.boundary — and a fabricated boundary is worse than a missing one, because it reads as decided. The schema’s job is to surface the questions; humans answer them (next lesson takes over from there).

💡Schema-gated means merge-gated

All three design choices are inert unless the schema runs in CI: a control file that fails validation fails the merge, exactly like a failing test. That single wiring decision is what turns the catalog from documentation into a system - it is the same move the DevSecOps Foundations training makes with pipeline templates (Templates Are Code), applied to compliance. If your catalog can drift from its schema, you have the binder again, just in YAML.

KNOWLEDGE CHECK

Meridian's team proposes simplifying the schema: drop the sampling.strategy enum for a free-text 'assessmentApproach' field, since teams complained the three values did not fit every case. What does this lesson predict, and what is the better fix?

Key takeaway

Structure the program as three layers — universal catalog (lint-forbidden system IDs), per-system config, per-run outputs — so the second authorization is a sibling directory, not a fork. One schema-gated YAML per control, with the schema doing assessor work: mandatory scope boundaries, closed-vocabulary sampling, evidence as a named contract. Generate skeletons with your AI CLI, but let FIXMEs surface what no tool should invent. Next: the statements humans still write — and how to make them survive assessment.

Search lessons