Development record
abcd tells every repo the truth about its fence
Press Release
The doctor reports what a managed repository's environment actually holds — and what it cannot — one honest verdict per fence piece, for every user from a bare local checkout to an organisation.
Alice, a hobbyist keeping their project in a plain local git repository — no forge, no CI — runs the same
abcd ahoydoctor as Bob, a solo developer on a personal GitHub account, and Carol, the maintainer of an org-hosted project with outside collaborators. None of them is told to become someone else first. The doctor probes read-only, and renders one verdict per fence piece from a small vocabulary drawn strictly from what a read-only probe can decide for this caller: applied (with its stated limit — for a forge-side required check this asserts registration, never a living executor), applied but drifted from the committed mirror, absent (not configured; where the plan cannot be read, enablement-undetermined is said rather than guessed), unverifiable for this caller (nogh, no auth, admin-only read — never rendered green), rule without executor (an in-tree rule nothing enforces), and unsupported (no forge, or a forge abcd has no adapter for — an environment fact, never an error)."It doesn't nag me to buy an organisation," said Alice. "It tells me what my setup protects, what it can't, and exactly what I'm trusting to luck."
Why This Matters
Captured 2026-07-17, the day abcd-cli's own main was protected by hand.
Extended 2026-08-19 from a full day of building the collaborator fence on
this repository manually, recorded in
2026-08-19-collaborator-fence-field-research.md,
and reworked the same day after two independent adversarial reviews. The
evidence reframes the original scope: the fence is a capability ladder
whose rungs exist or not depending on forge, plan, ownership — and caller,
since verdicts are caller-relative (an admin-only read is unverifiable to a
non-admin collaborator on the same repo). The tool's job for the amateur
coder is to say truthfully which rung they stand on, in the idiom the
banlist's reach: lines already ship.
Per enforcement-claims-are-facts,
a doctor verdict is a report, never enforcement — and the vocabulary's own
limits are part of the report (registration is not a living executor; absent
is not proof of unavailability). The trust rules the doctor and any later
apply verb answer to are adr-44:
never mutate a remote uninvited; identity from caller-local facts only.
What's In Scope
- The read-only doctor pass over an enumerated fence-piece registry (the spec's first deliverable is that closed list — no "every piece" claim without it), rendering the verdict vocabulary above per piece, per caller.
- The live-vs-mirror drift diff against
.abcd/work/rulesets/(closes iss-277), normalising server-assigned ids and timestamps so a re-apply is not a false drift. - The fence-piece registry extends beyond branch rulesets to the full
API-visible repo configuration. Repo settings (merge options,
delete_branch_on_merge, auto-merge), Actions permissions with the default workflow-token scope, and the security-and-analysis toggles (secret scanning, push protection, Dependabot) each get a verdict from the same vocabulary. A setting the public API cannot read or set is reported unsupported (API) with the manual step named, never a false green. The fork-PR outside-collaborator approval policy is the known case, its Actions/accessendpoint being private/internal-only. The live-vs-mirror drift diff extends to arepo-settings.jsonmirror sibling (iss-2608270512210664). Applying any of this stays the separate adr-44-bound apply intent below. - The pull-request-review verdict is maintainer-count-aware. A solo repo where
the author cannot self-approve is correctly served by
required_approving_review_count: 0, whereas a repo with multiple maintainers expects a non-author approval (count >= 1, with GitHub blocking self-approval). The doctor reports this piece against that context rather than a fixed threshold; setting the value is the apply intent's job. (GitHub'srequire_extra_approval_for_unattributed_changesalready dovetails with the four-role attribution model.) - The tier summary: which rung this repository stands on, and what that rung does not hold, in plain words.
- Loud degradation throughout (
loud-staging): a probe that cannot answer says so; the summary never renders green above an unverifiable piece.
What's Out of Scope
- Applying remote configuration — a separate intent, bound by adr-44 rule 1, owning the bootstrap ordering the field note records twice.
- The launch-gate refusal policy (what a cut refuses on, per tier) — a decision record of its own once the doctor has field experience; until it exists the launch preflight makes no claim on these verdicts.
- Non-GitHub forge adapters — such forges report unsupported, honestly, rather than a fake unverifiable.
- Cost/plan advice (what the user's tier makes free) — planning-interview material, fed by field-note lesson 8.
Acceptance Criteria
- Given Alice's repository has no git remote, When the doctor runs, Then every forge-side piece in the registry reports unsupported naming the absent remote, the local pieces (hooks, banlist, guard, preflight) report their true state, and the run exits as a supported state — never an error.
- Given Bob's personal public repository with secret scanning not enabled, When the doctor runs, Then the piece reports absent with the enabling lever, and where the plan cannot be read the report says enablement-undetermined rather than choosing between off and unavailable.
- Given Carol's live ruleset differs from the committed mirror under
.abcd/work/rulesets/, When the doctor runs, Then the piece reports applied but drifted, naming the rule that moved (iss-277). - Given a probe that lacks its tool, authentication, or permission (no
gh, unauthenticated, an admin-only read as a non-admin), When the doctor runs, Then that piece reports unverifiable for this caller, and a summary containing any such piece does not render green. - Given an in-tree rule with no executor (release retention, iss-282), When the doctor runs, Then the piece reports rule without executor — distinct from applied and from absent.
- Given a ruleset-required status check whose workflow does not exist on the default branch, When the doctor runs, Then the piece reports applied together with its stated limit — registration was verified, a living executor was not — and never a bare green.
SOTA
Surveyed on 2026-10-23; sources, evidence tiers and the local fit risks are in the forge, CI and merge SOTA note. It builds on the 2026-08-19 field research this intent already cites.
- What a non-admin can read is sharp, and was tested: without any
credential,
GET /repos/{o}/{r}/rules/branches/{branch}andGET /repos/{o}/{r}/rulesetsanswered 200 on a public repository; classic branch protection answered 401; the repository record carriedvisibilitybut notdelete_branch_on_mergeorallow_auto_merge; an organisation'smembers_can_*fields were absent (probe of 2026-10-23). - The documented edges match: "Anyone with read access to a repository
can view its active rulesets";
bypass_actorsis returned only with write access to the ruleset; rules in "evaluate" rulesets are excluded and "evaluate" is Enterprise-only;security_and_analysisneeds admin, owner or security manager; merge-related settings needcontents:readpluscontents:write(GitHub REST docs). - Scorecard models the caller axis and resolves it the wrong way for
abcd: its Branch-Protection check is a five-rung ladder;
EnforceAdmins,DismissStaleReviews,RequireLastPushApproval,RequiresStatusChecksandUpToDateBeforeMerge"require an admin token"; without one, "scores will be determined as if all such requirements have been met", andEnforceAdminsis false if any bypass actor exists. The ladder is worth borrowing; the assume-met rule is the false green loud-staging forbids. - The forge's CLI has the read-only probe:
gh ruleset check [branch]shows the rules that apply from wherever they are configured, for a branch that need not exist. - "Registration is not a living executor" is documented: a required workflow a filter skips stays "Pending" forever; GitHub's advice is not to require one that can skip.
- The apply side has a precedent that reports before it fixes: Allstar's
Branch Protection policy defaults to
log, withissueandfixopt-in. - The HTTP status is part of the verdict, read with the caller and the body: GitHub returns 404 for a private resource without authentication "to avoid confirming the existence of private repositories", so a 401, and a 404 whose body is "Not Found", mean "unverifiable for this caller"; but classic protection on an unprotected branch answers an authorised caller with 404 and the body "Branch not protected" (probed on 2026-10-23 with a token that can read the repository's settings), which means "absent". The registry records the body text each verdict keys on.
Path 2: a native doctor over an enumerated registry, shaped to Scorecard's Branch-Protection ladder for the tier summary and to the rulesets API's visibility edges for the caller-relative verdict (status plus caller plus body, so a 404 reads as absent only for a caller who can see the settings and only with the body that says so), with one forge adapter per piece as the seam; rulesets are read before classic protection because the former answers an anonymous reader. Scorecard as a dependency would need sign-off and is not recommended. The independent fit-challenge runs at plan time.
Open Questions
- The caller axis needs modelling at planning: multiple remotes (which is
"the forge"?), GHES/tenant hosts where
ghauth and features differ, and how a verdict says "unverifiable for you, verifiable for an admin". - The advisory-admin state: without
enforce_admins, protection is advisory against the admin's own token, and reading that flag is itself an admin-only probe. Where readable it is a stated limit under applied; where not, unverifiable — is that honest enough for the solo maintainer, the tool's primary persona? - Which protection shape is "protected enough" per tier? Force-push and
deletion blocks plus required checks as the floor; required reviews
deadlock solo maintainers; the two-reviewer external rule (iss-281) is
org-tier only. Per-repo template in
.abcd/config? - Cost advice: the doctor could say what the user's tier makes free (public-repo runner minutes against private-repo multipliers) — worth a column, or noise?
- (SOTA 2026-10-23) Whether this doctor and itd-2610161647248332's capability probe share one registry, each piece naming its least-privileged read and the statuses that mean applied, absent and unverifiable.
- (SOTA 2026-10-23) Whether the tier summary borrows Scorecard's rung names so a person can set it beside a Scorecard result, given its scoring rule is rejected.
Audit Notes
Empty. Populated by intent-fidelity-reviewer when intent moves to shipped/.
Frontmatter
| id | itd-92 |
|---|---|
| type | intent |
| lifecycle | drafts |
| severity | minor |
.abcd/development/intents/drafts/itd-92-abcd-verifies-branch-protection-on.md
open on GitHub ↗ · commit history ↗
Referenced by3
- adr-43Contributions are inbound = outbound MIT, and the trust boundary is the organisation's role ladderrelated
- adr-44abcd never mutates a remote uninvited, and identity derives from caller-local factsrelated
- itd-2609221842494980A dependency bump lands without a person re-authoring it, and no machine enters the contributor graphbuilt on by
mentions15
- adversarial-review-scales-with-blast-radiusAdversarial review scales with blast radius
- dcn-26102221050368442026-10-22 — The status-branch ruleset is applied by the person by hand, by design, and C15 is corrected to say so (facilitator, on the delegation of 2026-10-14; iss-2610222040438015, itd-2609151838312703): every ruleset in .abcd/work/rulesets is applied by the person with the README's gh api command and refreshed into the mirror, and invariant 10 lets abcd change a remote only through a dedicated, confirmed apply verb, which no record ever planned for rulesets; so C15's last clause, 'applied through the rulesets' own apply verb', was a drafting slip, not a promise owed, and becomes 'applied by the person from that mirror'. The outcome C15 asks for (the forge refuses deletion and force-push under abcd-status/) is unchanged and live as ruleset 25302453. A verb that applies or verifies rulesets would belong with the branch-protection doctor draft (itd-92), not here (grounds: .abcd/work/rulesets/README.md; brief invariant 10; adr-44)
- iss-211Multi-project adoption raises the priority of itd-91 (AI-attribution preference) and itd-92 (branch-protection verification) from 'good hygiene' to 'the product claim'. What abcd-cli has today is its OWN answer hard-coded: scripts/check-attribution.sh names specific banned footers and a fixed required trailer, and .github/workflows/attribution.yml is a hand-written file in one repo. Neither is portable, which is exactly the gap itd-91 already states — a project adopting abcd inherits abcd's preference by reading its docs but has no first-class way to declare its own (Co-Authored-By, none at all, or house style). The durable home is a rule in internal/core/lint beside deliverystate, indexdrift and citations: reachable from CLI and plugin, run in any managed repo, reading a declared per-repo preference rather than a constant. Two further observations from the 2026-08-11 session. (1) SCAFFOLD IS THE DELIVERY MECHANISM. internal/core/launch/scaffold renders exactly three artefacts into a managed repo today (release.yml, auto-release.yml, the runbook); an attribution workflow becomes the fourth, so every managed repo inherits the gate from one template instead of someone copying a file. It would automatically gain the self-scaffold parity property — and with it the dependabot pin problem of iss-209, which the sync tool merged in PR #215 already handles. The work just landed is the delivery rail for making this portable. (2) ITD-92 GATES EVERYTHING ELSE. A scaffolded gate that no repo adds to its required status checks is decoration; across N repos the required-check wiring and the enforce_admins policy stop being settings a maintainer clicks and become policy abcd asserts and verifies. That makes itd-92 arguably the higher-leverage of the pair: it is what converts every abcd-scaffolded check, present and future, from advisory into binding. itd-92's own capture note already says this in miniature — 'captured 2026-07-17, the day abcd-cli's own main was protected by hand — work the tool should carry for every repo it manages'. Both intents are still ungrilled drafts.
- iss-277Ruleset drift check is unbuilt: the applied branch rulesets are mirrored under .abcd/work/rulesets/ but nothing diffs the live rulesets against the committed JSON, so drift is invisible until a human re-reads the console; itd-92 owns the verb
- iss-281Externally submitted PRs and issues must be reviewed by at least two invited collaborators (hard rule, maintainer decision 2026-08-19). PR half: enforceable as a required status check counting approvals from collaborators with role triage or above, running on pull_request_target so a fork cannot neuter it. Issues half: GitHub issues carry no native review gate — ledger captures land via PRs and inherit the PR rule; tracker issues rely on intake S1 process until a mechanism exists
- iss-282The retention prune has no executor: ComputeRetention renders the newest-per-line plan on every cut (brief section 3) and ship.go names GitHub Release + retention prune as a later phase, so superseded patches (v0.5.0, v0.4.0 today) stay published until someone deletes them by hand from the plan. First rung: a documented manual prune after each publish (gh release delete, tags kept for record); the executor belongs in the release workflow after that proves out
- iss-321persona pronoun discipline itd-79 is violated by gendered pronouns in seven intent records including a shipped intent
- iss-338Seven registry personas are given he/she/his/her pronouns across intents/drafts (itd-92,97,98,108,126,128) and intents/shipped (itd-100), violating the they/them persona discipline (itd-79); review-enforced convention with no mechanical gate, concentrated in one 2026-08 authoring batch — a cosmetics-batch cleanup
- iss-2608270512210664Repo-object settings that gate merge behaviour (delete_branch_on_merge, allow_merge_commit/allow_squash_merge/allow_rebase_merge) have no source-of-truth in the tree: the .abcd/work/rulesets/ mirror covers branch RULESETS only (protection, review, merge queue), not the repository-object settings that live behind the separate GET repos/{owner}/{repo} API. delete_branch_on_merge was enabled 2026-08-27 with no accompanying tree record, which the rulesets README's own rule forbids ('refreshed by hand in the same change as the settings edit it records'). Fix: add a repo-settings.json snapshot sibling to the ruleset mirror (or fold repo settings into that mirror) and record the merge-hygiene settings there; itd-92's drift check should then cover it.
- iss-2608270636272755abcd's remote-config apply verb should enable GitHub native secret scanning AND secret-scanning push protection by default on every managed repo (belt-and-braces alongside the CI gitleaks full-history scan): push protection blocks a secret at push time, earlier than CI, and secret scanning covers the default branch continuously. Enabled by hand on [redacted-user]/abcd 2026-08-27 via PATCH /repos/{o}/{r} security_and_analysis (both were disabled). Notes: push protection requires secret scanning enabled first; both are free on public repos; secret_scanning_non_provider_patterns and secret_scanning_validity_checks remain optional further hardening. Homes: the read-only VERIFY side is itd-92 (the doctor now reports these toggles); the APPLY-by-default belongs to the separate adr-44-bound apply intent itd-92 defers to, adjacent to itd-106 (abcd sets up the CI a repo requires); the desired state should be mirrored in the repo-settings.json sibling (iss-2608270512210664).
- itd-115A Ready PR Merges Without Ever Wedging BEHIND
- itd-153abcd's remote-config apply verb should enable GitHub native secret scanning AND secret-scanning push protection by default on every managed repo (belt-and-braces alongside the CI gitleaks full-history scan): push protection blocks a secret at push time, earlier than CI, and secret scanning covers the default branch continuously. Enabled by hand on [redacted-user]/abcd 2026-08-27 via PATCH /repos/{o}/{r} security_and_analysis (both were disabled). Notes: push protection requires secret scanning enabled first; both are free on public repos; secret_scanning_non_provider_patterns and secret_scanning_validity_checks remain optional further hardening. Homes: the read-only VERIFY side is itd-92 (the doctor now reports these toggles); the APPLY-by-default belongs to the separate adr-44-bound apply intent itd-92 defers to, adjacent to itd-106 (abcd sets up the CI a repo requires); the desired state should be mirrored in the repo-settings.json sibling (iss-2608270512210664).
- itd-2610161647248332abcd finds out what the forge can do before choosing a route
- itd-2610290858115394A repository's maintainers hold its quality gate: a contributor's change waits for a maintainer's approval, and only a maintainer starts a release, adopts an intent or makes private work public
- spc-46abcd-s-remote-config-apply-verb-should-enable-github-native