Skip to content

The canary is public, so its non-merge boundary is enforced by the repository

Date: 2026-08-15 Status: open Supersedes: none — resolves blocker C of docs/findings/20260814-1130-p3-c0-canary-credential-unsatisfiable.md Superseded-by: none — current Phase: PTV-SCF-0001 P3 · Verification harness (C0) Terminal state of this record: a visibility ruling plus the branch protection it unlocks, applied and verified by command output. It creates no credential and does not close G-P3-1.

docs/findings/20260814-1130-p3-c0-canary-credential-unsatisfiable.md blocker C established that condition 3 of the conforming-credential set — “branch protection on the canary’s default branch requiring a review the App cannot supply to itself” — has no mechanism on the org’s current configuration:

$ gh api orgs/petrova-codes
plan: free | owned private repos: 4
$ gh api repos/petrova-codes/petrova-canary/branches/main/protection
403 — "Upgrade to GitHub Pro or make this repository public to enable this feature."
$ gh api repos/petrova-codes/petrova-canary/rulesets
403 — "Upgrade to GitHub Pro or make this repository public to enable this feature."

Branch protection and rulesets are both paid-plan features for private repositories only. Both are free on public repositories. The finding named three ways out and deliberately took none of them, because the choice is the operator’s.

petrova-codes/petrova-canary becomes a public repository, and its default branch carries branch protection that the probe credential cannot satisfy.

This is option 1 of the finding’s three. It is chosen over the other two on four grounds, in order of weight:

  1. It keeps the boundary in the repository, where the P0 audit says it has to live. Option 3 (amend condition 3 to “the App simply does not hold a permission that permits a direct push”) relocates the non-merge property from the repository to the token’s grant. The P0 credential audit already established that a boundary enforced in one place and not the other is a convention, not a boundary — the PRIVILEGED_PATH_RE cross-finding is the precedent, and the whole scaffold exists to find defects of exactly that shape. Option 3 would have P3 certify a boundary using the reasoning P0 rejected.
  2. It amends nothing. The canary ruling’s five conditions (2026-08-12-purpose-built-canary-supersedes-pwplz.md:38-52) never mention visibility. Condition 2 enumerates its disqualifiers exactly — “no published surface, no domain, no package, no stars, no inbound links, no history anyone would miss” — and public is not among them, nor entailed by any of them. So this record ratifies a configuration consistent with the ruling rather than superseding a condition. Option 3 would require a supersession that weakens a condition; option 1 requires neither.
  3. It costs nothing and blocks on nobody. Option 2 (GitHub Pro/Team) reaches the same end state, preserves privacy, and is strictly a superset of this option’s capability — at a recurring cost, for a repository whose defining property is that it is disposable. If the org acquires a paid plan later for unrelated reasons, this record does not need reversing; it is a choice of mechanism, not a bet against paying.
  4. The pwplz inference is not being repeated. The finding flagged the risk honestly: “an empty repo with one README is arguably not a surface — but ‘arguably’ is the word that produced the pwplz error.” The distinction is that pwplz’s inertness was inferred from a missing registry field, and the canary’s is constructed and positively checked. The ruling’s own method rule demands the positive form, and it is discharged below with command output, not with an empty column.

What “not a published surface” is checked against

Section titled “What “not a published surface” is checked against”

Per the method rule at …purpose-built-canary-supersedes-pwplz.md:104-107, each disqualifier gets a positive check rather than an absence:

Condition-2 disqualifierCheckState
published surfacecontents are one README.md; no site, no Pages, no buildnone
domainno CNAME, no DNS record, not in registry.yaml url: as a live hostnone
packageno manifest of any ecosystem in the treenone
starsstargazers_count0
inbound linksno reference outside this control plane’s own recordsnone
history anyone would misssingle initial commitnone

Public visibility changes exactly one thing about that table: it makes the absence verifiable by anyone, which is a strengthening of the ruling’s method rule rather than an exception to it.

A public repository can acquire the very properties condition 2 forbids — a star, an inbound link, a fork — without anyone acting. That is a real cost of this option and it is not waved away:

  • Issues, projects and the wiki are disabled at ratification, so the repo has no surface that invites participation.
  • The README.md states in its first line that the repository is a disposable write-probe target with no product meaning.
  • Acquiring a star, fork, or inbound link is a supersession trigger, not a tolerance. If the check table above stops reading clean, the canary is deleted and recreated (condition 5 exists for this) or this record is superseded. A drifted canary is not quietly kept.
  • GitHub Pro / Team on the org (finding option 2). Rejected as the default, not as wrong. It preserves privacy and reaches an identical enforcement posture, at recurring cost, for a repository defined by being disposable. It remains available and would not require reversing this record.
  • Amend credential condition 3 (finding option 3). Rejected on ground 1 above. The finding itself named this as “the one an agent under delivery pressure would choose: it needs no money and no human account action” — and it is being explicitly declined here, with the reason recorded, so that the decline is auditable rather than assumed.
  • Rulesets instead of classic branch protection. Equivalent on the free plan once public. Classic branch protection is chosen because condition 3 is phrased in its vocabulary and because gh api …/branches/main/protection is the exact call the finding already ran, so the before/after evidence is directly comparable.
  • Leave C0 blocked pending a paid plan. Rejected: it stalls P3 on a billing decision when a free mechanism satisfies the condition as written.

For the canary:

  • petrova-codes/petrova-canary is public. Issues, projects and wiki disabled.
  • main carries branch protection with required pull request reviews, applied to administrators, with force-push and deletion denied.

For the credential:

  • Condition 3 becomes evaluable for the first time. It is not thereby satisfied — satisfaction is the negative test in condition 5, which is G-P3-1, and which still requires the single-repo App that does not yet exist (blocker B).
  • Nothing here reduces what act 3 must produce. It fixes what act 3 is tested against.

For the probe:

  • Probe output on the canary is world-readable. This is acceptable and is stated rather than assumed: probe writes are synthetic, carry no secret, and the App credential is held outside the repository per blocker B’s third act. No probe may ever write real fleet state, a token, or a customer-shaped value to the canary — a public canary makes that a disclosure, not merely a hygiene failure.

For invariants:

  • INV-1 remains violated for the reasons the ruling already gives; a canary bounds intent, not capability. Nothing here changes that.
  • No MR added, modified, or repealed.

Applied and checked in-session; output quoted in the PR that carries this record. The claim this record must survive is narrow: that the two 403s in blocker C no longer reproduce, and that protection is actually present on main.

A pre-publication audit of the tree was run, because making a repository public is irreversible in effect even where it is reversible in setting. It turned up one correction to a merged record, stated rather than silently absorbed: …-p3-c0-canary-credential-unsatisfiable.md:34 describes the contents as “a single README.md (1,629 bytes) and nothing else”. The tree also holds SCRATCH.md (122 bytes), which declares itself probe scratch space. Both are inert and neither carries a secret, so condition 2 still reads clean — but the finding’s inventory was one file short, and that is a defect in a claim the rule: in its own front-matter holds it to. Recorded here for P3’s verification round; the finding is not edited (L3).

  • docs/findings/20260814-1130-p3-c0-canary-credential-unsatisfiable.md:99-140 — blocker C, the three options, and the warning about option 3.
  • docs/decisions/2026-08-12-purpose-built-canary-supersedes-pwplz.md:38-52,104-107 — the five conditions, and the method rule requiring a positive check.
  • docs/findings/20260811-2027-ptv-scf-0001-credential-scope.md — the five credential conditions, condition 3 in its original wording.
  • docs/decisions/2026-08-14-ptv-scf-0001-p3-open.md — G-P3-1, which this does not close.
  • registry.yaml:792-812 — the canary entry, fleets_allowed: [] until the installation exists.
  • Subagent: PTV-SCF-0001 P3 · C0 (session 2026-08-15)
  • Human: ☑ (proxy) countersign — that the canary is public, that branch protection is the mechanism satisfying credential condition 3, that finding option 3 is declined on the record, and that star/fork/inbound-link drift is a supersession trigger.
    • Countersigned by human:devarno on 2026-08-15, by explicit directive in session (“follow justified best-practice recommendation(s) for (2)”). Ticked by the agent as scribe, not as signatory: the human act is the directive, and this line is its record.