Skip to content

PTV-SCF-0001 P3 · C0 — canary registered and credential scoped, G-P3-1 PASS

Date: 2026-08-15 Status: open Supersedes: none Superseded-by: none — current Phase: PTV-SCF-0001 P3 · Verification harness Chunk: C0 (milestone M3.1) Gate: G-P3-1 — “The canary repo exists, appears in registry.yaml, and its credential is scoped to it alone — demonstrated by that credential failing against a second governed repo.” Terminal state of this record: one gate evaluated PASS against command output, and the five canary conditions closed out. It does not close P3, does not touch state/, and evaluates no other gate.

G-P3-1 PASS. All three of its clauses are separately evidenced below, and its operative clause — demonstrated by that credential failing — is satisfied by a refusal that was executed, not asserted.

This closes the last of the three human acts named in docs/findings/20260814-1130-p3-c0-canary-credential-unsatisfiable.md:153-164. Act 1 (countersign) and act 2 (blocker C) merged in #282; act 3 (the App) was performed by the operator this session, and is verified here.

Conditions from docs/decisions/2026-08-12-purpose-built-canary-supersedes-pwplz.md:38-52, countersigned 2026-08-15.

#ConditionStateEvidence
1created to be a canary, named soMETpetrova-codes/petrova-canary, created 2026-08-12
2empty of anything worth keepingMETREADME.md + SCRATCH.md, both inert; 0 stars, 0 forks; issues/projects/wiki disabled
3registered role: canary with real url:METregistry.yaml:792-812, the file’s only role: canary entry
4reachable by exactly one credentialMET — newlyinstallation 153988870, repository_selection: selected, total_count: 1
5disposableMETno domain, no package, no inbound links, single-commit history

Condition 4 was the sole outstanding item at the finding. It is now met.

App petrova-canary-probe, id 4605881 — distinct from petrova-act’s 3662455, so this is a new credential and not a re-scoping of the estate-wide one the finding disqualified.

$ GET /app
{"slug":"petrova-canary-probe","id":4605881,
"permissions":{"contents":"write","metadata":"read","pull_requests":"write"}}
$ GET /app/installations
{"id":153988870,"account":"petrova-codes","repository_selection":"selected",
"permissions":{"contents":"write","metadata":"read","pull_requests":"write"}}
$ GET /installation/repositories (installation token)
{"total_count":1,"repos":["petrova-codes/petrova-canary"]}

Against the five conforming-credential conditions (docs/findings/20260811-2027-ptv-scf-0001-credential-scope.md), which petrova-act failed on 1 and 2:

#ConditionState
1scoped to exactly one repositoryPASS — selected, total_count: 1
2contents: write, pull_requests: write, metadata: read and not workflowsPASS — exactly those three, nothing else; no workflows, no administration, no issues, no organization_projects
3unable to merge — branch protection it cannot satisfyPASS — see the refusals below
4distinct from C-1 and C-3, separate storePASS — separate App id, private key in its own .pem outside the shared env file and outside _loader.zsh
5verified by a negative test that is a P3 deliverablePASS — this record

Condition 2’s “and not workflows” matters beyond hygiene: administration was also withheld, which is what stops the App from removing the very branch protection that enforces condition 3. A credential that can unprotect the branch satisfies condition 3 only until it decides not to.

The gate’s operative clause. Run under an installation token for 153988870:

petrova-codes/petrova GET=404
petrova-codes/kahn-hq GET=404
petrova-codes/eva-hq GET=404
petrova-codes/petrova-canary GET=200

404, not 403. Repositories outside the installation are not merely forbidden to this credential — they are invisible to it. That is a stronger result than the gate asks for: a 403 would prove the boundary is enforced at the authorisation layer, where a 404 shows the credential has no name for the governed estate at all.

Stated rather than smoothed: the corresponding write attempt against those three repos was not executed. The session’s tool-permission classifier refused a PUT /contents against governed repositories, correctly — a mutation attempt against production repos is indistinguishable at the call site from an attack, and a probe that must be allowed to write to prove it cannot write is a bad shape regardless of intent. Read-invisibility is the stronger evidence and it was obtained; the unexecuted write attempt is recorded here as a known gap in the test’s coverage rather than described as passed. If P3’s verification round judges the read-only form insufficient, the write form should be run against a purpose-made throwaway second repo, never against a governed one.

Under the same installation token, on the canary, with branch protection applied in #282:

$ POST /git/refs (branch probe/g-p3-1) → 201
$ PUT /contents/SCRATCH.md (on probe/g-p3-1) → 200
$ POST /pulls → 201 PR #1
$ PUT /pulls/1/merge → 405
"New changes require approval from someone other than the last pusher."
$ PUT /contents/SCRATCH.md (branch: main) → 409
"Could not update file: Changes must be made through a pull request."

The App can do exactly what the probe needs — branch, commit, open a PR — and cannot land it by either route. The self-merge is refused by require_last_push_approval, and the protection bypass is refused by the pull-request requirement. Both refusals come from the repository, not from the token’s grant, which is the property 2026-08-15-canary-public-for-branch-protection.md chose option 1 to preserve.

petrova-codes/petrova-canary#1 is left open as the gate’s evidence artefact.

  • M3.1 moves to closed. G-P3-1 is the first of the ten G-P3-* gates to be evaluated, and it passed on evidence rather than on waiver — which matters given P3 inherits a phase closed on two waived gates.
  • PTV-FND-0024 is fully discharged and can be closed at P3’s verification round. It should be closed there, not here: classification is a round’s act.
  • registry.yaml’s fleets_allowed: [] on the canary entry (:797) may now be revisited, since its own note (:812) gates that on the single-repo installation existing. Not done here — registry edits are PR-only and this record does not authorise one.
  • M3.2 (C1 · probe skeleton) is unblocked and is the next chunk.
  • The probe must resolve its target from registry.yaml by role: canary, never from the literal slug, per the canary ruling’s §Consequences.
  • It does not claim P3 is closable. Nine of ten gates are unevaluated.
  • It does not classify PTV-FND-0024, or the new items raised at P3’s open (F-40, F-41). That is a verification round’s act.
  • It does not claim the negative test is complete — see the unexecuted write attempt above.
  • It does not touch state/petrova-codes.yaml, which remains unpopulated (F-40).

Operator-side notes, recorded because they are credential hygiene

Section titled “Operator-side notes, recorded because they are credential hygiene”
  • petrova-canary.pem was created mode 0644 — world-readable on a multi-user system. Set to 0600 this session. A private key at 0644 is a condition-4 defect in substance even where the store is nominally separate.
  • PETROVA_CANARY_CLIENT_SECRET is present in the env file and is an OAuth credential. App authentication uses the JWT + private-key path only, so it is unused. An unused secret at rest is pure downside; recommend deletion unless a user-facing OAuth flow is planned.
  • docs/decisions/2026-08-14-ptv-scf-0001-p3-open.md:224,245 — G-P3-1 and M3.1 as seeded.
  • docs/findings/20260814-1130-p3-c0-canary-credential-unsatisfiable.md:153-164 — the three human acts, all now discharged.
  • docs/decisions/2026-08-12-purpose-built-canary-supersedes-pwplz.md:38-52 — the five canary conditions.
  • docs/findings/20260811-2027-ptv-scf-0001-credential-scope.md — the five conforming-credential conditions.
  • docs/decisions/2026-08-15-canary-public-for-branch-protection.md — the branch protection condition 3 is enforced by.
  • https://github.com/petrova-codes/petrova-canary/pull/1 — the evidence artefact.
  • Subagent: PTV-SCF-0001 P3 · C0 (session 2026-08-15)
  • Human: FLIGHT<alex@devarno.com:2026-09-22> — that G-P3-1 is PASS on the evidence above, that the negative test’s unexecuted write half is an accepted gap rather than a failure, and that M3.1 is closed