Skip to content

cosign automerge

The four registry-edit verbs (petrova_act_registry_edit, petrova_act_reconcile_drift, petrova_act_register_consumer, petrova_act_deregister_consumer) default to human-merge-only per MR-12 (control-plane firewall). The PR is opened with the awaiting-human-merge label and the bot does not enable auto-merge.

The cosign path is an explicit operator-authorised opt-out from that default. It exists for high-volume environments where the human-merge friction outweighs the firewall value (e.g. a nightly drift-reconciliation batch). Three gates must align:

GateSetPurpose
Verb optioncosign: true (or --cosign on CLI)Caller-level opt-in
Env: master switchPETROVA_COSIGN_AUTOMERGE=1Operator-level opt-in
Env: second tokenPETROVA_COSIGN_TOKEN=<pat>”Second principal” — possession of a second secret

Optional: PETROVA_COSIGN_IDENTITY=<name> — sets the Co-Authored-By: trailer identity. Default: petrova-cosign.

When all three align:

  • PR is labelled cosign-automerge (replacing awaiting-human-merge).
  • Co-Authored-By: <identity> <cosign@petrova.host> is appended to the rationale → survives squash-merge into the commit message.
  • The PR body’s verb-specific summary names the cosign identity.
  • enableAutoMerge(squash) is called via the pr-emitter.

Without any one of the three gates, behavior is unchanged (human-merge default).

  1. Mint a second GitHub PAT distinct from PETROVA_GITHUB_TOKEN. The PAT need only have permission to enable auto-merge on petrova-codes/petrova — its presence is the “second key,” not its capability surface.
  2. Choose a cosign identity name (e.g. nightly-drift-reconciler).
  3. Set both env vars in whatever runs the verb (CI job, scheduled container, etc.):
    Terminal window
    export PETROVA_COSIGN_AUTOMERGE=1
    export PETROVA_COSIGN_TOKEN=ghp_
    export PETROVA_COSIGN_IDENTITY=nightly-drift-reconciler

CLI (any of the registry-edit verbs):

Terminal window
echo '{"slug":"choco-hq"}' | \
petrova petrova_act_reconcile_drift petrova-codes --apply --cosign

Programmatic (SDK):

await reconcileDrift({
targetRepo: "petrova-codes",
slug: "choco-hq",
triggeredBy: { kind: "schedule", ref: "nightly-drift-cron" },
cosign: true,
});

After the PR opens:

Terminal window
gh pr view <N> --json labels,autoMergeRequest
# Expect labels to include 'cosign-automerge' and autoMergeRequest to be non-null.

After CI passes, the PR merges automatically. The squash-merge commit will carry both the bot’s authorship and the cosign Co-Authored-By: trailer.

Cosign-merged PRs are easy to surface for review:

Terminal window
gh pr list -R petrova-codes/petrova --label cosign-automerge \
--state merged --limit 100

The ADRs co-emitted by each verb invocation are unaffected — same docs/decisions/<date>-<verb>-<slug>.md shape — but the body’s “Sign-off” section will name the cosign identity rather than a human countersign.

Use cosign when:

  • The verb is invoked on a schedule by a non-human actor.
  • The change is mechanical (drift reconciliation matches state-sweep signal; the SHAs are deterministic).
  • A human reviews the aggregate via the labelled-PR audit query weekly/monthly.

Don’t use cosign when:

  • The change is substantive (petrova_act_registry_edit with novel ops; petrova_act_register_consumer for a new fleet member).
  • The fleet is small enough that per-PR human review is cheap.
  • The cosign identity is the same human as the primary actor — the “second principal” guarantee dissolves.

Unset PETROVA_COSIGN_AUTOMERGE (or set it to anything other than "1"). The next verb call falls back to human-merge default. No code change required.

MR-12 says “CLAUDE.md is a projection, not a source” — the registry is the source. Cosign doesn’t change what gets written to the registry; the verb’s substrate validation still runs. The firewall MR-12 protects is “agent automation alone cannot modify the control plane” — and the cosign path preserves that: an operator must possess the second secret. Auto-merge is the delivery mechanism once both principals have authorised.