Note #24 recorded the four vuln-intake CORE primitives landing as reusable modules — dedup keys, DSPy free-text signatures, EPSS canonicalisation, and CVSS v3.1 parsing — and left two questions open in plain view. How do CVSS and EPSS combine into the single severity input the playbook intent reasons over? And what shape does the operator’s per-playbook configuration take when it crosses from the CACAO source into the compiled artefact?
This wave answers both, and closes the long-running golden refresh that the OTel emitter wave left in its wake.
What landed in this wave
A fifth primitive: severity policy that composes CVSS and EPSS
CVSS is a base-score arithmetic over a vector grammar. EPSS is a probability and a percentile over the same advisory. Neither is, on its own, the severity input a vuln-intake playbook acts on — the playbook needs a single ordered decision that reads “act now”, “queue”, or “defer”, and that decision sits on top of both inputs.
The wave that landed the four content primitives left that composition step out by design: each primitive carries its own tests, its own changelog, its own version surface, and the policy that composes them sits one layer up.
That layer landed in this wave:
- PR #211 — CORE-SEVERITY. The module imports the CVSS base-score primitive and the EPSS canonicalisation primitive, composes them through a declarative policy surface — a default policy keyed on CVSS bands with EPSS overrides, and a contributor-overridable policy hook that lets a downstream playbook substitute its own composition — and emits a single ordered decision the playbook intent reasons over. The composition is a content concern, not a per-target concern; every compile target reads the same severity decision because every compile target imports the same shared module.
The vuln-intake primitives package now reads five-deep in
compilers/_shared/primitives/ — dedup, DSPy signatures, EPSS, CVSS,
and severity-policy — and the five together are the reusable content
layer the per-target CORE wiring beats consume on the next move.
EXTEND-config: CACAO playbook variables, documented
The CACAO source the worked examples carry has a playbook_variables
shape — operator-facing configuration that crosses from the portable
playbook into the compiled artefact each compile target writes. The
shape is small, but the decisions it carries are not: what counts as
a variable versus a constant, what shape the variable’s type
declaration takes, how the three compile targets each render it into
their own configuration surface, and where the variable’s default
value lives when the operator does not override it.
The CACAO specification answers some of those questions and leaves others to the downstream tooling. This wave records the answers the project commits to:
- PR #212
— EXTEND-config docs for CACAO
playbook_variables. The decision documented in operator-facing form: the typed variable surface, the default-value semantics, the per-target rendering — Temporal as a workflow input, LangGraph as a graph state binding, n8n as a workflow-level constant — and the validation gate that runs at compile-time before any target emits.
The docs land in the framework’s operator-facing surface, not the contributor-facing one. A contributor reads the primitive’s own tests and changelog to learn what the primitive does; an operator reads the EXTEND-config page to learn what the variable surface they will touch looks like. Both audiences get their own surface.
F-CR-04 three-target parity matrix: the golden refresh closes
PR #198 —
the long-running stale-golden refresh on the
ci/three-target-parity-matrix branch — was opened to absorb the OTel
emitter wrap the F-CR-04 wave landed across the three targets through
PRs
#180,
#183,
#186, and
#190. The
emitter wrap changes the artefact-shape contract the byte-parity
goldens record — every artefact a compiler writes now carries a span
the OTel emitter named — and every golden cell that pre-dates the
emitter wrap needs to be regenerated against the new shape.
This wave closes the refresh down to a single remaining cell:
- Seven Temporal goldens regenerated to absorb the emitter-wrap artefact shape.
- Nine LangGraph goldens regenerated to absorb the same.
- Sixteen cells across the two targets reviewed and merged through the parity-matrix branch.
The one cell remaining is an n8n ransomware-containment example from the SKELETON era of the framework, whose artefact shape pre-dates several intervening waves. Rather than refresh that cell in place, the project routes its work through a CORE-uplift card — the cell will return on the byte-parity matrix once its content has been walked up to the CORE line that the other parity cells now sit on.
With sixteen cells closed, the three-target parity matrix reads green-but-one for the first time since the OTel emitter wave opened. Every Temporal and LangGraph parity cell on the matrix now records the artefact shape the OTel-emitter-wrapped compilers write; every n8n parity cell but the one held back for CORE-uplift records the same.
What this wave unlocks next
The five-primitive vuln-intake content layer is now the input the per-target F-WF-01 CORE wiring beats consume. Each target’s CORE wiring imports the dedup keys, the DSPy free-text signatures, the EPSS canonicalisation, the CVSS v3.1 parsing, and the severity-policy decision — and renders them into the artefact the target executes. The wiring is per-target; the content the wiring renders is shared.
The CACAO playbook_variables documentation lands the operator-facing
surface in time for the next wave’s worked-example READMEs to point
at it. A contributor walking in from the launch post — and an
operator reading the worked example to decide what to commit to —
both find the variable surface documented in the form the artefact
actually carries.
The closed parity matrix is the audit-trail handle the next wave hands off to: a contributor opening a worked-example PR sees the byte-parity goldens read green across two of the three targets and green-but-one across the third, and the diff their PR introduces records exactly against that baseline.
Community lane status
The M3 community-ignition launch post — “Open for contributors” —
remains the entry point against five good-first-issues on
secops-ng-framework: issues
#193,
#194,
#195,
#196,
and
#197.
The shipping cadence under the launch post stays daily; a contributor
walking in this week finds the same five issues open, a worked-example
surface reading two-deep on the canonical axis, a vuln-intake content
layer reading five-deep in compilers/_shared/primitives/, and a
parity matrix reading green-but-one across the three compile targets.
What this wave does not promise
It does not promise F-WF-01 CORE wiring is in place on any compile target yet. The five-primitive content layer is the input; the wiring that imports the layer into the per-target emitters is the next beat, one card per target.
It does not promise the n8n ransomware-containment cell returns to the byte-parity matrix in this wave. The cell sits behind a separate CORE-uplift card and returns to the matrix once its content has been walked up to the CORE line.
It does not promise the CACAO playbook_variables documentation
generalises to the alert-triage worked example yet. The doc lands
against the surface every CACAO source exposes, and the alert-triage
EXTEND beats — the operator-facing READMEs that walk a reader through
the alert-triage variable surface — are subsequent beats on the
alert-triage axis.
It does not promise severity-policy closes the vuln-intake content layer. Severity is the composition step the four prior primitives called for; the layer’s shape is now five-deep, and subsequent content primitives — what the playbook does with the severity decision once it is in hand — sit on top of the five and arrive on later waves.
Where the work is
secops-ng-framework— CORE-SEVERITY composes CVSS and EPSS (PR #211); EXTEND-config documents CACAOplaybook_variables(PR #212); the three-target parity matrix’s golden refresh (PR #198) closes sixteen cells across Temporal and LangGraph.secops-ng-website— this note and the twenty-four that preceded it, plus the M3 launch post on PR #32.github.com/secops-ng— the issues, the good-first-issues open against the community lane, the auto-generated roadmap.
Walk the framework repo today and the vuln-intake content layer reads five-deep in shared primitive form, the operator-facing variable surface is documented in its own EXTEND-config page, and the three-target byte-parity matrix reads green across every Temporal and LangGraph cell with one n8n cell held back for CORE-uplift. The composition step is landed, the operator surface is named, and the parity matrix is one cell away from a clean grid.
More from the lanes as F-WF-01 CORE wiring lands across the three compile targets, the n8n ransomware-containment cell walks its way back to the matrix through CORE-uplift, and the vuln-intake content layer grows beyond severity into what the playbook does with the decision once it is in hand.