MACGUARD · DOCUMENTATION
Español

Log and checkpoints

What is recorded, how checkpoints are signed and how rollbacks are detected.

The log

logs/log.jsonl is an append-only log with one JSON line per event (time, level, event, message and details). A readable view is on the transparency page. Events carry counts only: never names or findings of quarantined rules.

Checkpoints

A checkpoint is a three-line text: the origin macguard-rules-log v1, the number of entries and the root in base64. publish.yml signs it with Sigstore (checkpoint.sigstore.json) and attaches it to the release (checkpoint.txt).

Diagram: Publishing and signing the checkpoint

Drawing the diagram…

Diagram description

publish.yml appends the approval event, writes an unsigned checkpoint with tlog.py, signs it with cosign using the workflow identity, re-verifies the whole log, then seals the manifest which embeds the log head, signs the manifest and writes the inclusion proof that ties the release entry to that checkpoint.

Mermaid code
sequenceDiagram
  autonumber
  participant W as publish.yml
  participant T as tlog.py
  participant CO as cosign
  participant SE as seal.py
  W->>T: event release.approved
  W->>T: checkpoint (origin, size, root)
  W->>CO: sign-blob checkpoint
  CO-->>W: checkpoint.sigstore.json
  W->>T: verify (every checkpoint, consistency, signatures)
  W->>SE: write manifest.json (rules_root and log head)
  W->>CO: sign-blob manifest.json
  CO-->>W: manifest.sigstore.json
  W->>T: release-proof (inclusion proof for the candidate.accepted entry)
  T-->>W: inclusion-proof.json
Diagram: Checkpoints over time

Drawing the diagram…

Diagram description

Each signed checkpoint commits to a log size and root. Later checkpoints extend earlier ones. A consistency proof shows that the tree of the earlier size is a prefix of the later tree, so entries were only appended. A client that remembers the earlier checkpoint can refuse a smaller tree (rollback) or a different root at the same size (fork).

Mermaid code
flowchart LR
  C20["checkpoint<br/>size 20<br/>00000020.txt"]:::sec
  C75["checkpoint<br/>size 75<br/>00000075.txt"]:::sec
  CN["next checkpoint<br/>size N"]:::sec
  C20 -->|consistency.json<br/>proof 20 to 75| C75
  C75 -->|consistency proof| CN
  RB["client sees size smaller<br/>than the one it trusts"]:::qua -.->|rollback refused| C75
  FK["client sees another root<br/>at the same size"]:::qua -.->|fork refused| C75
  classDef src fill:#cfe3ff,stroke:#2f6df0,color:#0b1b33
  classDef val fill:#ffe9b8,stroke:#b8860b,color:#2b1d00
  classDef sec fill:#cdeedd,stroke:#1b6e3c,color:#08210f
  classDef pub fill:#e4d7ff,stroke:#6a3fd1,color:#1b0b3b
  classDef qua fill:#ffd4cc,stroke:#c0392b,color:#33100b
  classDef gate fill:#ffffff,stroke:#111111,stroke-width:3px,color:#111111

The signed manifest includes the log head (log.origin, log.size, log.root) and must match the attached checkpoint. In the latest release the size is 75 and the log root 1bab9bb9364b3a42f76835ae138bffad33eaa3694d0021afd208f5fd54e63d96.

Events

These are the event codes emitted by the workflows and scripts. A documentation test checks that each one appears in the file shown.

CodeLevelWhen it is recordedSource
sources.fetchedINFOUpstream sources were fetched at pinned commits.scripts/tlog.py
candidate.classifiedINFOClassification summary: examined, accepted, quarantined, rejected and deprecated (counts only).scripts/tlog.py
candidate.acceptedINFOThe approved set passed every mandatory check; carries its candidate rules_root.scripts/tlog.py
candidate.not_publishableWARNINGThis run produces no release; the blockers are listed.scripts/tlog.py
rules.withheldWARNINGPreviously published files that no longer pass the checks and are withheld until resolved.scripts/tlog.py
file.skippedWARNINGA source file was skipped while preparing the candidate (size, binary, path not allowed).scripts/tlog.py
rule.rejectedERRORLegacy event of the earlier flow: a file failed compiler validation.scripts/tlog.py
gate.rule_errorERRORLegacy event: a file failed when loaded by the app.scripts/tlog.py
candidate.rejectedERRORLegacy event: the whole candidate was blocked by failing files.scripts/tlog.py
candidate.changesINFOThere is an effective change against the latest release; publication is requested..github/workflows/daily.yml
candidate.unchangedINFONo effective change: no release and no approval request..github/workflows/daily.yml
run.failedERRORThe daily run failed before it could decide; nothing was published..github/workflows/daily.yml
release.approvedINFOThe candidate was approved for signing (automatically or by a person, depending on the environment) and the log head is signed..github/workflows/publish.yml
release.publishedINFOThe signed release was published on GitHub and a site deployment was requested..github/workflows/publish.yml
release.failedERRORSigning or publishing failed; nothing new was published..github/workflows/publish.yml
site.github_pages_deployedINFOSite deployed to GitHub Pages..github/workflows/site.yml
site.github_pages_failedERRORThe GitHub Pages deployment failed..github/workflows/site.yml
site.live_verifiedINFOThe public URL was verified with the bytes it serves (package, files, signatures, proofs, links)..github/workflows/site.yml
site.live_failedERRORThe public URL verification failed..github/workflows/site.yml
site.build_failedERRORThe site could not be built or verified..github/workflows/site.yml
site.cloudflare_deployedINFOSite deployed to Cloudflare Pages automatically from the workflow..github/workflows/site.yml
site.cloudflare_skippedWARNINGNo Cloudflare credentials are configured in the repository; no deployment was attempted..github/workflows/site.yml
site.cloudflare_blockedERRORThe Cloudflare credential preflight failed; no deployment was attempted..github/workflows/site.yml
site.cloudflare_failedERRORThe Cloudflare Pages deployment failed; the previous site stays online..github/workflows/site.yml