Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

DomainModeDescription
controlsinitialDraft new narratives and evidence for controls
controlsissues-fixAddress platform issues on existing controls
controlsnotes-fixDeprecated alias for issues-fix
controlsreview-fixFix findings from a family review job
policyanswerGenerate answers for policy questions
policyreview-fixFix findings from a policy review
scopeanswerGenerate answers for scope questions
scopereview-fixFix 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 \
  --system "My System" --framework-id fedramp-moderate

Fix Controls after Family Review

pretorin campaign controls --mode review-fix --family AC --review-job <job-id> \
  --system "My System" --framework-id fedramp-moderate

Options

OptionDescription
--systemTarget system ID or name (required)
--framework-idTarget framework ID (required)
--familyControl family ID or abbreviation, case-insensitive (e.g., AC, AU). List them with pretorin frameworks families <framework-id>
--controlsSpecific control IDs (comma-separated)
--all-controlsTarget all controls in the framework
--modeCampaign mode: initial, issues-fix, notes-fix, review-fix (required)
--all-open-issuesTarget controls with open issues in the system/framework context
--issue-sourceOptional issue source filter
--issue-controlOptional comma-separated issue control filter
--issue-familyOptional issue family filter
--include-resolvedInclude resolved issues in issue discovery
--artifactsArtifact 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-jobReview job ID (required for review-fix mode)
--concurrencyNumber of parallel workers (default: 4)
--max-retriesMaximum retry attempts per item (default: 2)
--checkpointPath to checkpoint file for resume
--applyApply proposals to platform after completion
--outputOutput 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

OptionDescription
--policiesSpecific policy IDs (comma-separated)
--all-incompleteTarget all incomplete policies
--modeCampaign mode: answer, review-fix (required)
--systemOptional system context passthrough
--concurrencyNumber of parallel workers (default: 4)
--max-retriesMaximum retry attempts per item (default: 2)
--checkpointPath to checkpoint file for resume
--applyApply proposals to platform after completion
--outputOutput mode: auto, live, compact, json

Scope Campaigns

Answer Scope Questions

pretorin campaign scope --mode answer \
  --system "My System" --framework-id fedramp-moderate

Options

OptionDescription
--systemTarget system ID or name (required)
--framework-idTarget framework ID (required)
--modeCampaign mode: answer, review-fix (required)
--concurrencyNumber of parallel workers (default: 4)
--max-retriesMaximum retry attempts per question (default: 2)
--checkpointPath to checkpoint file for resume
--applyApply proposals to platform after completion
--outputOutput mode: auto, live, compact, json

Checking Campaign Status

pretorin campaign status --checkpoint .pretorin/campaigns/controls-initial-20260808-091500.json

--checkpoint is required — campaign status reads the run’s state from that file, so there is no active-context fallback. --output accepts the same auto, live, compact, and json modes as the campaign commands.

When a campaign command is run without --checkpoint, the checkpoint is written to a timestamped default path, .pretorin/campaigns/<domain>-<mode>-<YYYYMMDD-HHMMSS>.json. The prepared-run output prints the exact pretorin campaign status --checkpoint ... invocation for that file; pass --checkpoint explicitly if you would rather choose the path yourself.

Campaign Lifecycle

  1. Prepare — The campaign snapshots platform state and creates a local checkpoint file
  2. Claim — Items are claimed with TTL-based leases (safe for multiple agents)
  3. Draft — Each item gets full context and drafting instructions
  4. Propose — Proposals are submitted without writing to the platform
  5. 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.

Issue Admission and Evaluation

A campaign proposal may recommend an Issue only when it supplies the complete issue-create contract: one in-scope expectation key, exact unmet expectation, one independently observed gap with its source/workspace basis, risk basis and ratings, a concrete clearance condition, and a non-empty minimum-evidence list. Apply re-reads current Issues and reuses a matching expectation before writing. Malformed evidence recommendations, unknown evidence types, missing context, source/recipe gaps, optional advice, and subtasks remain proposal warnings; they are never synthesized into Issues.

When a new Issue lands, apply creates one minimal draft remediation plan on that same Issue. A plan failure produces one needs_input action and never a replacement or child Issue. issues-fix stays bounded to existing Issues: campaign output may propose verification, but generic apply never bypasses the governed verification_pendingverify closure path.

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. Evidence keys are scoped to the campaign run; Issue keys are stable for one system/framework/control/expectation so a fresh campaign cannot create a second Issue for the same expectation.

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 evidence writes are new. Issue admission still reuses the stable expectation key and current-Issue dedupe read.
  • 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

Evidence keys are derived per artifact and sent on each evidence batch item:

{run_id}:{item_id}:{artifact_type}:{revision}:{index}
  • artifact_type is evidence for the campaign batch path.
  • index is 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.
  • revision is 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.

Canonical Issue creation instead hashes this stable identity:

system_id + framework_id + control_id + expectation_key

The resulting issue-expectation:<sha256> key deliberately ignores campaign run and proposal revision. Reusing the key with changed content conflicts rather than silently creating a second Issue for the same expectation. Both formats are internal to the CLI; the platform treats them as opaque strings.

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:

ValueMeaning
supportedThe platform enforces client keys — retries are protected
unsupportedThe platform answered but ignores keys — protection falls back to local receipts
unknownThe 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 responseMeaningWhat the CLI does
created / okNew resource writtenReceipt records ok; downstream steps run
replayedKey matched an earlier writeReceipt records replayed with the original resource ids; treated as applied, but no downstream step re-fires
idempotency_key_conflictSame key reused with different contentHard 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.