Campaigns
The Problem
Real organisations have dozens or hundreds of expectations. A CISO's expectation that "all services are secure" is not a single check — it depends on sub-expectations about patching, access controls, encryption, and monitoring. These expectations form a natural hierarchy, and evaluating them all at once is both slow and hard to report on.
How WRIT Handles It
WRIT organises expectations into campaigns using the step towards clause. A campaign is an expectation at the top of a hierarchy. Sub-expectations declare that meeting them is a "step towards" meeting the campaign.
Campaign Structure
graph TD
A["All services are secure"] --> B["All services are patched"]
A --> C["All services have access controls"]
A --> D["All services are encrypted"]
B --> E["Critical patches applied<br/>within 7 days"]
B --> F["Non-critical patches applied<br/>within 30 days"]
C --> G["No shared accounts on<br/>production services"]
style A fill:#ecfeff,stroke:#06B6D4,color:#0e7490
style B fill:#ecfeff,stroke:#06B6D4,color:#0e7490
style C fill:#ecfeff,stroke:#06B6D4,color:#0e7490
style D fill:#ecfeff,stroke:#06B6D4,color:#0e7490
style E fill:#ecfeff,stroke:#06B6D4,color:#0e7490
style F fill:#ecfeff,stroke:#06B6D4,color:#0e7490
style G fill:#ecfeff,stroke:#06B6D4,color:#0e7490
DSL Syntax
A campaign is an expectation whose step towards is "__CAMPAIGN__":
expectation: "all_services_secure"
expector: "all_stakeholders" ? "id" = "CISO"
partition: "is_secure" of "all_services" of "ITService"
expect empty: "is_false"
expectee: SELF->"owner"
actions:
- "Review and remediate security posture"
step towards: "__CAMPAIGN__"
Sub-expectations reference their parent:
expectation: "all_services_patched"
expector: "all_stakeholders" ? "id" = "PATCH_LEAD"
partition: "is_patched" of "all_services" of "ITService"
expect empty: "is_false"
expectee: SELF->"owner"
actions:
- "Apply outstanding patches"
step towards: "all_services_secure"
Scoped Evaluation
All evaluation in WRIT is scoped to a campaign. When you evaluate a campaign, WRIT:
- Finds the campaign expectation.
- Collects all expectations that are steps towards it (direct and transitive).
- Builds a scoped registry containing only the entity types, entity sets, and data relevant to those expectations.
- Evaluates all expectations within the scoped registry.
This scoping means:
- Faster evaluation — Only relevant data is loaded and processed.
- Clearer reports — Results are grouped by campaign, not scattered across the entire domain.
- Independent progress — Different teams can focus on different campaigns.
Campaign Status
A campaign's overall status is derived from its sub-expectations:
| Sub-expectation Results | Campaign Status |
|---|---|
| All met (true) | Met — the campaign goal is achieved |
| Any not met (false) | Not met — at least one expectation has failures |
| Any unknown, none false | Uncertain — data gaps prevent a definite answer |
Worked Example
A simple two-level campaign for service ownership:
expectation: "ownership_campaign"
expector: "all_stakeholders" ? "id" = "CISO"
partition: "has_owner" of "all_services" of "ITService"
expect empty: "is_false"
expectee: SELF->"owner_lead"
actions:
- "Review service ownership programme"
step towards: "__CAMPAIGN__"
expectation: "owners_contactable"
expector: "all_stakeholders" ? "id" = "SERVICE_MGMT"
partition: "owner_has_email" of "owned_services" of "ITService"
expect empty: "is_false"
expectee: SELF->"owner"
actions:
- "Update contact details for service owner"
step towards: "ownership_campaign"
Evaluating ownership_campaign:
- First checks that all services have owners.
- Then checks that all owned services have contactable owners.
- Reports are grouped under the campaign, showing progress towards the overall goal.
Common Pitfalls
- Circular step-towards references — Expectation A steps towards B, and B steps towards A. This creates an infinite loop. WRIT detects this and reports an error.
- Orphaned expectations — An expectation with no
step towardsclause that is not marked as"__CAMPAIGN__"will not be evaluated. Every expectation must either be a campaign root or step towards another expectation. - Over-deep hierarchies — While WRIT supports arbitrary depth, very deep hierarchies are hard to report on. Two or three levels usually suffice.
What to Read Next
- Data Flow — How the entire pipeline fits together from data to insight.
- Writing Expectations (DSL) — Campaign and step-towards syntax.
WRIT Docs