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).
Drawing the diagram…
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 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.jsonDrawing the diagram…
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
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:#111111The 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.
| Code | Level | When it is recorded | Source |
|---|---|---|---|
sources.fetched | INFO | Upstream sources were fetched at pinned commits. | scripts/tlog.py |
candidate.classified | INFO | Classification summary: examined, accepted, quarantined, rejected and deprecated (counts only). | scripts/tlog.py |
candidate.accepted | INFO | The approved set passed every mandatory check; carries its candidate rules_root. | scripts/tlog.py |
candidate.not_publishable | WARNING | This run produces no release; the blockers are listed. | scripts/tlog.py |
rules.withheld | WARNING | Previously published files that no longer pass the checks and are withheld until resolved. | scripts/tlog.py |
file.skipped | WARNING | A source file was skipped while preparing the candidate (size, binary, path not allowed). | scripts/tlog.py |
rule.rejected | ERROR | Legacy event of the earlier flow: a file failed compiler validation. | scripts/tlog.py |
gate.rule_error | ERROR | Legacy event: a file failed when loaded by the app. | scripts/tlog.py |
candidate.rejected | ERROR | Legacy event: the whole candidate was blocked by failing files. | scripts/tlog.py |
candidate.changes | INFO | There is an effective change against the latest release; publication is requested. | .github/workflows/daily.yml |
candidate.unchanged | INFO | No effective change: no release and no approval request. | .github/workflows/daily.yml |
run.failed | ERROR | The daily run failed before it could decide; nothing was published. | .github/workflows/daily.yml |
release.approved | INFO | The 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.published | INFO | The signed release was published on GitHub and a site deployment was requested. | .github/workflows/publish.yml |
release.failed | ERROR | Signing or publishing failed; nothing new was published. | .github/workflows/publish.yml |
site.github_pages_deployed | INFO | Site deployed to GitHub Pages. | .github/workflows/site.yml |
site.github_pages_failed | ERROR | The GitHub Pages deployment failed. | .github/workflows/site.yml |
site.live_verified | INFO | The public URL was verified with the bytes it serves (package, files, signatures, proofs, links). | .github/workflows/site.yml |
site.live_failed | ERROR | The public URL verification failed. | .github/workflows/site.yml |
site.build_failed | ERROR | The site could not be built or verified. | .github/workflows/site.yml |
site.cloudflare_deployed | INFO | Site deployed to Cloudflare Pages automatically from the workflow. | .github/workflows/site.yml |
site.cloudflare_skipped | WARNING | No Cloudflare credentials are configured in the repository; no deployment was attempted. | .github/workflows/site.yml |
site.cloudflare_blocked | ERROR | The Cloudflare credential preflight failed; no deployment was attempted. | .github/workflows/site.yml |
site.cloudflare_failed | ERROR | The Cloudflare Pages deployment failed; the previous site stays online. | .github/workflows/site.yml |