Campaign Workflows
The campaign command group runs bulk compliance operations across multiple controls, policies, or scope questions in a single coordinated run. Campaigns support an external-agent-first pattern with checkpoint persistence and lease-based concurrency for safe fan-out to multiple agents.
Campaign Domains and Modes
| Domain | Mode | Description |
|---|---|---|
controls | initial | Draft new narratives and evidence for controls |
controls | issues-fix | Address platform issues on existing controls |
controls | notes-fix | Deprecated alias for issues-fix |
controls | review-fix | Fix findings from a family review job |
policy | answer | Generate answers for policy questions |
policy | review-fix | Fix findings from a policy review |
scope | answer | Generate answers for scope questions |
scope | review-fix | Fix findings from a scope review |
Control Campaigns
Draft New Narratives for a Family
pretorin campaign controls --mode initial --family AC \
--system "My System" --framework-id fedramp-moderate
Fix Controls with Platform Issues
pretorin campaign controls --mode issues-fix --all-open-issues
Fix Controls after Family Review
pretorin campaign controls --mode review-fix --family AC --review-job <job-id>
Options
| Option | Description |
|---|---|
--system | Target system ID or name |
--framework-id | Target framework ID |
--family | Control family to target (e.g., AC, AU) |
--controls | Specific control IDs (comma-separated) |
--all-controls | Target all controls in the framework |
--mode | Campaign mode: initial, issues-fix, notes-fix, review-fix |
--all-open-issues | Target controls with open issues in the system/framework context |
--issue-source | Optional issue source filter |
--issue-control | Optional comma-separated issue control filter |
--issue-family | Optional issue family filter |
--include-resolved | Include resolved issues in issue discovery |
--artifacts | Artifact types to generate: narratives, evidence, or both (default: both). Apply is evidence-first: narratives cite the created evidence ids, so narratives alone is refused for a control with no citable evidence — use both. |
--review-job | Review job ID (required for review-fix mode) |
--concurrency | Number of parallel workers |
--max-retries | Maximum retry attempts per item |
--checkpoint | Path to checkpoint file for resume |
--apply | Apply proposals to platform after completion |
--output | Output mode: auto, live, compact, json |
Policy Campaigns
Answer All Incomplete Policy Questions
pretorin campaign policy --mode answer --all-incomplete
Fix Policy Review Findings
pretorin campaign policy --mode review-fix --policies <policy-id>
Options
| Option | Description |
|---|---|
--policies | Specific policy IDs (comma-separated) |
--all-incomplete | Target all incomplete policies |
--mode | Campaign mode: answer, review-fix |
--system | Optional system context passthrough |
--concurrency | Number of parallel workers (default: 4) |
--max-retries | Maximum retry attempts per item (default: 2) |
--checkpoint | Path to checkpoint file for resume |
--apply | Apply proposals to platform after completion |
--output | Output mode: auto, live, compact, json |
Scope Campaigns
Answer Scope Questions
pretorin campaign scope --mode answer \
--system "My System" --framework-id fedramp-moderate
Options
| Option | Description |
|---|---|
--system | Target system ID or name (required) |
--framework-id | Target framework ID (required) |
--mode | Campaign mode: answer, review-fix |
--concurrency | Number of parallel workers (default: 4) |
--max-retries | Maximum retry attempts per question (default: 2) |
--checkpoint | Path to checkpoint file for resume |
--apply | Apply proposals to platform after completion |
--output | Output mode: auto, live, compact, json |
Checking Campaign Status
pretorin campaign status --checkpoint .pretorin/campaign-checkpoint.json
Campaign Lifecycle
- Prepare — The campaign snapshots platform state and creates a local checkpoint file
- Claim — Items are claimed with TTL-based leases (safe for multiple agents)
- Draft — Each item gets full context and drafting instructions
- Propose — Proposals are submitted without writing to the platform
- Apply — All accepted proposals are pushed to the platform in one operation
Use --apply to automatically apply after completion, or run campaign status to review before applying.
Idempotency and Replay
Apply is safe to retry. Every campaign create write carries an idempotency key the platform uses to recognize a repeat of a write it already committed, so a crash or a lost response leads to a replay rather than a duplicate.
The run id
When a campaign checkpoint is created, the CLI mints a per-run UUID and stores it
in the checkpoint file as run_id:
{
"version": 2,
"identity": { "domain": "controls", "mode": "initial", "...": "..." },
"run_id": "3f6c2a1e-9d84-4c17-8f0b-1a2b3c4d5e6f",
"idempotency_support": "supported",
"items": { "...": "..." }
}
run_id is run state, not campaign identity — it sits beside identity,
never inside it, so resuming a checkpoint still passes identity validation.
- Resuming or retrying the same checkpoint reuses the same
run_id, so keys match the earlier attempt and the platform replays it. - A deliberate fresh campaign (a new checkpoint file, or a deleted one) mints a
new
run_id, so its writes create new resources. - Checkpoints written before this feature have no
run_id; apply mints one and flushes it to disk before its first platform write.
Do not hand-edit or copy run_id between checkpoints. Copying it into an
unrelated campaign makes that campaign’s writes collide with the original run’s
keys.
Key scheme
Keys are derived per artifact and sent on each evidence batch item and on control-issue creation:
{run_id}:{item_id}:{artifact_type}:{revision}:{index}
artifact_typeisevidenceorissue.indexis the artifact’s position in the stored proposal — not its offset in the request being sent, so a resume that re-sends only the un-receipted subset reuses each artifact’s original key.revisionis the item’s proposal revision. Submitting a new proposal for an item that already has apply receipts bumps it, so the new content writes under fresh keys. A key must map to exactly one payload for the resource’s lifetime; without this, re-drafting a failed item would reuse a key the platform had already bound to the previous draft and fail permanently.
The format is internal to the CLI; the platform treats the key as an opaque string and never parses it.
Checking whether the platform enforces keys
Apply probes GET /api/v1/public/capabilities once at the start of each run and
records the answer in the checkpoint as idempotency_support:
| Value | Meaning |
|---|---|
supported | The platform enforces client keys — retries are protected |
unsupported | The platform answered but ignores keys — protection falls back to local receipts |
unknown | The probe could not get an answer (auth, rate limit, outage, network) |
unknown is deliberately distinct from unsupported: an error response says
nothing about the deployed build, so it is never read as “the platform doesn’t
support this.” The probe decides only what a run may claim about duplicate
protection. Keys are always sent regardless of the result — a platform version
that predates the feature ignores the field, so sending is free, and backing off on
a flaky probe would drop the protection exactly when the network is unreliable.
The value is re-probed every run and never trusted from a previous run’s
checkpoint: a server that started mid-migration can legitimately flip. The per-write
proof is always the item’s own replayed status, not the capability flag.
Replay semantics
| Server response | Meaning | What the CLI does |
|---|---|---|
created / ok | New resource written | Receipt records ok; downstream steps run |
replayed | Key matched an earlier write | Receipt records replayed with the original resource ids; treated as applied, but no downstream step re-fires |
idempotency_key_conflict | Same key reused with different content | Hard per-item failure; the platform’s remediation message is surfaced |
A replayed item is a success: its original evidence ids are still cited by the narrative, and a resume will not re-send it. Control campaigns do not post a separate completion note: the legacy notes endpoint has no idempotency key, so no client can guarantee exactly-once notification after a committed response is lost. The durable artifact receipts are the completion record.
An idempotency_key_conflict is not retryable — re-running apply on the same
checkpoint derives the same key and conflicts again. It means a key was reused for
different content, which normally indicates a hand-edited checkpoint. To get new
keys, either submit a fresh proposal for the item (which bumps its revision and
supersedes the conflicting content) or re-prepare the campaign into a new
checkpoint file so it mints a new run_id. The error message names both.
Keys are unique within your organization and honored for the lifetime of the resource. Platform versions that predate idempotency-key support ignore the field, so sending it is always safe; against those versions, retry protection falls back to the CLI’s local receipts, which narrow the crash/retry duplication window rather than closing it.