Skip to content

docs(plan): correct Phase 3c security-gate scope and 3d ordering - #294

Merged
kyle-sexton merged 2 commits into
mainfrom
docs/plan-3c3d-corrections
Jul 29, 2026
Merged

docs(plan): correct Phase 3c security-gate scope and 3d ordering#294
kyle-sexton merged 2 commits into
mainfrom
docs/plan-3c3d-corrections

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Two factual corrections to docs/topics/claude-review-lanes/PLAN.md, the
approved specification. Corrections only — no requirement is added, removed,
or re-scoped beyond the two named defects, and no phase tag moves (tag
inventory verified byte-identical against origin/main for [DOING],
[DONE], [TODO], [GATE], [USER-RESERVED], [USER-APPROVAL GATE],
[FALLBACK …], [EXEC-SHAPE]).

Defect A — Phase 3c's security-gate check was under-scoped. It asked only
that the claude-code-plugins ruleset string security-review / security-review
be intact. But claude-code-plugins carries zero repo-local rulesets
(gh api repos/…/rulesets?includes_parents=false[]); the gate is org
ruleset 19388547, whose repository_property condition scopes it to repos
where the custom property requires-security-review is "true". That property
is org_actors-editable and defaults "false", and is currently true on
exactly one of fourteen repos. So flipping the property on a repo — or
inverting the condition's own property_values — arms or disarms the required
check with the context string byte-identical. The old check would have passed
cleanly against a disarmed gate, and Phase 3d, which distributes the security
caller fleet-wide, is exactly when that drifts. The check is now a multi-part
invariant.

Defect A′ — Phase 3c's queue-overflow criterion was unobservable as written.
It asked for an "overflowed-cancelled caller conclusion observed". A run
cancelled while pending appears to leave no check run at all, so overflow
would present as check absence, not a cancelled conclusion. The criterion
now probes that discriminator directly, with pass = the required check
reporting a documented-pass status (success, skipped, neutral) and
absence treated as a failure mode of equal weight. The supporting observation
is recorded with both of its limits: it is one data point, and it came from
pending eviction under cancel-in-progress: false with no queue: key, which
is adjacent to queue: max overflow rather than the same mechanism.

Defect B — Phase 3d's ordering instruction was wrong and would have caused
the outage it prevents.
It said App access extends "BEFORE the manifest PR
merges". Exposure is symmetric: the operative gate is a cardinality check in
standards-sync.yml's attest job, which aborts before the missing/excess
set-diff is ever reached. Granting first fails
installation reports 10 repositories; expected 8; merging first fails the
inverse. attest is a needs: of sync, so the whole matrix is skipped —
fail-closed and self-clearing, nothing mutates, no target corrupted. The
mitigation is a tight window, not an ordering.

The 3d mechanism is corrected too: repository selection is
REST-addressable (PUT /user/installations/{installation_id}/repositories/{repository_id}),
so the extension is an API call sequenceable to seconds rather than a
hand-timed UI click. Selection is not the same surface as installation
permissions; 3a0's workflows: write grant stays the org-owner action it is
documented as. Three consequential one-liners repeating the wrong "BEFORE"
ordering are corrected in step: approval-record item 5, the [GATE] line, and
the user-approval-gate line.

Volatile line-number citations are deliberately not reintroduced (the old
text cited standards-sync.yml:309-355); the new text cites
standards-sync.yml, attest and the error-message fragment instead, so it
stays correct across the pending sync-engine re-pin.

Test plan

Documentation-only change; no runtime behavior. Verified mechanically:

  • markdownlint-cli2 on the file — 0 errors.
  • Encoding: zero U+FFFD; em-dashes preserved as U+2014 — counted against the
    current base, origin/main = 128 and this branch = 143, net +15, all of them
    in added lines; U+2013 unchanged at 7 both sides; file remains UTF-8 with LF
    endings. (An earlier figure in this description was measured before the rebase
    onto origin/main; the net delta was the same, the absolute counts were not.)
  • Wrap: added lines are ≤ 81 chars (three at 81), inside the file's own existing
    81–89 distribution for body prose; markdownlint MD013 is disabled here, so the
    ~80 wrap is convention rather than an enforced rule.
  • Scope: five hunks at exactly the five intended sites; phase-tag inventory
    byte-identical to origin/main.
  • The 3d bullet is re-wrapped as a whole to hold the wrap convention; the text
    after "Wave 1:" is proven word-identical to origin/main by a token-level
    comparison, so the reflow changed wrapping only.
  • Merge cleanliness: git merge-tree --write-tree against origin/main before
    and after docs(plan): record Phase 5 equivalence + sweep evidence #287 landed — clean auto-merge both times. Branch is now rebased
    onto origin/main at 9416a0d.

Every factual claim in the new text was re-derived from live state by command
before being written (org ruleset 19388547 detail, the custom-property schema
and its regenerated true-set, run 30172462100 and the check runs/suite at
d26dac6, the attest job source, the manifest-derived expected cardinality
of 8, installation 144867070's repository_selection, both new-target
repository ids, and the two quoted sentences against the current GitHub
OpenAPI description). An independent fresh-context verifier re-derived both
corrections with the rationale withheld; its findings are incorporated — the
three it raised as blocking (a cross-reference to a finding not present in
PLAN.md, an actor claim that outran what the docs support, and the
queue-overflow mechanism mismatch) are each fixed in the text above.

Related

No linked issue — this corrects the specification document for the in-flight
claude-review-lanes effort.

Two factual corrections to the approved specification. No requirement is
added, removed, or re-scoped beyond the named defects; no phase tag moves
(tag inventory verified byte-identical against origin/main).

Phase 3c's security-gate check was under-scoped. claude-code-plugins carries
zero repo-local rulesets — the gate is org ruleset 19388547, whose
`repository_property` condition scopes it to repos where the org-actor
editable, false-by-default custom property `requires-security-review` is
"true". Flipping either that property on a repo or the condition's own
property_values arms or disarms the required check with the ruleset context
string byte-identical, so the old check would have passed cleanly against a
disarmed gate. Phase 3d, which distributes the security caller fleet-wide, is
exactly when that drifts. The check is now a multi-part invariant: ruleset
active, empty bypass_actors, exact context string, the condition still
including the property == "true", AND the true-property repo set still equal
to {claude-code-plugins}.

Phase 3c's queue-overflow criterion named an artifact that may not exist. A
run cancelled while pending appears to leave no check RUN, so overflow would
present as check absence rather than a `cancelled` conclusion. The criterion
now probes that discriminator directly and treats absence as a failure of
equal weight. The supporting observation is recorded with both of its limits:
it is one data point, and it came from pending eviction under
`cancel-in-progress: false` with no `queue:` key, which is adjacent to
`queue: max` overflow rather than the same mechanism. The check suite at that
commit does conclude `cancelled` with zero runs, so the signal survives at
suite level while the required check — which the ruleset evaluates by
context — is absent.

Phase 3d's ordering instruction was wrong and would have caused the outage it
was meant to prevent. Exposure is symmetric: the operative gate is a
cardinality check in standards-sync.yml's `attest` job, so granting first and
merging first each wedge, in mirrored directions. The mitigation is a tight
window, not an ordering. The mechanism is corrected too — repository
SELECTION is REST-addressable under a classic PAT with repo scope, so the
extension is an API call sequenceable to seconds rather than a hand-timed UI
click. Selection is not the same surface as installation PERMISSIONS; 3a0's
grant stays the org-owner action it is documented as. Whether org-owned
installations gate above repo-admin is undocumented, so the PAT holder should
be an org owner rather than assume repo admin suffices.

The 3d bullet is re-wrapped as a whole to keep the ~80-char convention; the
text after "Wave 1:" is proven word-identical to origin/main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 48s —— View job


Review: Phase 3c/3d PLAN.md corrections

  • Gather context (PR diff, CLAUDE.md)
  • Understand the request (code review of docs-only PLAN.md corrections)
  • Review changed file against CLAUDE.md guidelines
  • Post findings

This is a documentation-only change to the approved PLAN.md spec (1 file,
+78/-23). No workflow/action files are touched, so the CLAUDE.md security
ground rules (trigger hygiene, SHA-pinning, least-privilege, log hygiene)
don't directly apply here — this review focuses on internal correctness and
consistency of the corrected text itself.

I independently re-derived the mechanical claims in the test plan (tag
inventory diff, wrap width, rebase point) and they check out: the
[DOING]/[DONE]/[TODO]/[GATE]/[USER-RESERVED]/[USER-APPROVAL GATE]/[FALLBACK]
tag lines are untouched — only their descriptive text changed — and the
branch is rebased cleanly on origin/main at 9416a0d.

Two findings from that pass:

1. The "org owner must hold the PAT" mitigation isn't propagated to all three restated lines (moderate)

3d's body (and the new [GATE] line under "Open questions") calls out an
explicit, UNDOCUMENTED-risk mitigation: since installation 144867070 is
org-owned, an org owner should hold the classic PAT rather than assuming
repo-admin suffices —
PLAN.md:669,
carried into
PLAN.md:974.

But the PR description says three "consequential one-liners" were corrected
in step (approval-record item 5, the [GATE] line, and the user-approval-gate
line), and only one of the three actually carries the "held by an org owner"
qualifier:

  • Approval record item 5 —
    PLAN.md:944-946:
    "...via the REST selection endpoint under a classic PAT." — no org-owner
    qualifier.
  • Open questions [GATE]
    PLAN.md:972-974:
    "...under a classic PAT held by an org owner." — has it.
  • Handoff user-approval-gate —
    PLAN.md:996-997:
    "...via the REST selection endpoint under a classic PAT." — no org-owner
    qualifier.

"Handoff to implementation" is exactly the section an implementer would
consult at execution time, and it's one of the two that drops the
qualifier. Since this detail exists specifically to mitigate a
gating-behavior unknown the doc itself flags as UNDOCUMENTED, it's worth
carrying into all three restatements rather than only the middle one — an
implementer skimming only the Handoff section would miss it.
Fix →

2. Minor grammar in the queue-overflow pass criterion (nit)

PLAN.md:635-637:
"Pass requires that required check reporting a documented-pass status
(success, skipped, or neutral); absence is a failure mode of equal
weight." — this is malformed ("requires that required check reporting"
doesn't parse). Since this sentence defines the actual pass/fail criterion
for the corrected queue-overflow check, worth tightening to e.g. "Pass
requires the required check to report a documented-pass status..." for an
unambiguous acceptance criterion.
Fix →

FYI, not a finding: the test plan's mechanical em-dash count
("115 → 130, all added") doesn't match what I get re-running the count
( occurrences: origin/main = 126, this branch = 140, net +14). The
delta is close (14 vs. claimed 15) but the absolute counts are off by ~11 in
both directions, so it looks like a different counting method was used
upstream rather than a real encoding problem — I confirmed separately there's
no U+FFFD and no stray mojibake in the changed hunks. Not blocking, just
flagging since it was presented as a verified number.

Everything else — the security-gate multi-part invariant (3c), the symmetric
grant/merge exposure analysis and REST-selection mechanism (3d), and the
three corrected [GATE]/approval-record/user-approval-gate sites — is
internally consistent and, as far as I can check without live GitHub API
access, an accurate correction of the two named defects. No scope creep
beyond the stated corrections was found in the diff.

@github-actions

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

Fix a malformed sentence in the queue-overflow pass criterion ("Pass requires
that required check reporting" did not parse) — it defines the acceptance
criterion, so it needs to be unambiguous.

Carry the "held by an org owner" qualifier into the other two restatements of
the Phase 3d gate (approval-record item 5 and the handoff user-approval-gate
line). It previously appeared only on the [GATE] line under Open questions,
but the handoff section is what an implementer reads at execution time, and
the qualifier exists specifically to mitigate a gating unknown the document
itself flags as UNDOCUMENTED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kyle-sexton
kyle-sexton merged commit 04d0d14 into main Jul 29, 2026
40 checks passed
@kyle-sexton
kyle-sexton deleted the docs/plan-3c3d-corrections branch July 29, 2026 14:36
kyle-sexton added a commit that referenced this pull request Jul 29, 2026
…onale (#295)

## Summary

Follow-up to #294, which auto-merged via this repo's loop-lane merge
automation
while this correction was still in review. #294 fixed two real defects
but
overreached on one of them, and the overreach is on `main` right now.

**What #294 got wrong.** It replaced Phase 3d's approved instruction —
"App
access extends **BEFORE** the manifest PR merges" — with a
tightest-window
instruction, justified by "Exposure is SYMMETRIC — neither order is the
safe
one." That premise does not survive re-derivation.

The failure *mode* is symmetric: either order trips the same cardinality
check.
The *trigger coupling* is not. The manifest PR merge **is itself** a
push to
standards `main`, and the standards caller passes no `standards-ref`, so
the
reusable's `main` default plans the **post-merge** manifest
(`standards-sync.yml`, `plan` job checkout). So:

- **Merge-first** self-triggers: the merge is the triggering push,
`attest` sees
8 installed against 10 expected, and the wedge is **certain and
immediate**.
- **Grant-first** triggers nothing. It wedges only if an unrelated push,
the
  weekly cron, or a `workflow_dispatch` lands inside the window.

`BEFORE` is the lower-risk order. It is restored verbatim in the 3d
pre-step,
approval-record item 5, the `[GATE]` line, and the user-approval-gate
line —
which means the approved decision is left standing, not overturned.

**What stays corrected**, because it was genuinely wrong: the original
rationale
claimed attestation "compares the FULL target set both directions". It
does not
get that far — the operative gate is a **cardinality** check that aborts
before
any set-diff, and `missing`/`excess` is only reachable on an
equal-cardinality
substitution. Grant-first's real cost — the App holds write access to
two repos
that are not yet manifest targets, for the window — is now stated rather
than
implied. The REST-selection mechanism, installation and repository ids,
and the
selection-vs-permissions distinction from #294 are unchanged.

**Four further corrections in the same pass:**

- **False recovery claim.** "If the window closes with no push, recovery
waits
for that cron" is wrong — `sync.yml` also carries `workflow_dispatch`
with a
  settable `dry-run` input, so recovery is immediate.
- **3c invariant was incomplete.** It pinned the `repository_property`
condition
but not `target` or `conditions.ref_name`. Each of those is a
single-field
disarm that leaves every pinned field byte-identical: retarget the
ruleset, or
move `~DEFAULT_BRANCH` from `include` to `exclude`, and #294's check
still
  goes green on a disarmed gate. Both are now pinned.
- **Pass statuses were over-attributed.** `success`/`skipped`/`neutral`
is
enumerated for *classic branch protection*; the rulesets docs never
enumerate
  conclusions, and gate `19388547` is a ruleset. Now recorded as the
  best-documented proxy rather than as documented fact for this surface.
- **Org-owner qualifier was overstated.** The docs require only "admin
access to
the repository"; org-owner gating is flagged UNDOCUMENTED in the body.
The
three summary lines now carry it as a **precaution**, matching the body.

**One edit outside the sites #294 touched, called out deliberately:**
the Phase 3
Sanity Check told the operator to verify the gate with
`gh api repos/melodic-software/claude-code-plugins/rulesets` "still
lists
`security-review / security-review`". That endpoint returns only
`{id, name, source_type, target, enforcement}` — the context string
never
appears, so the recipe is unexecutable, and #294's own thesis makes it
obsolete.
It is the same defect expressed as a command, so leaving it would have
it
contradict 3c a hundred lines below.

## Test plan

Documentation-only; no runtime behavior. Verified mechanically on this
branch:

- `markdownlint-cli2` — 0 errors.
- Encoding: zero U+FFFD, zero CR; em-dashes preserved as U+2014 (143 →
150 vs
  `origin/main`, all in added lines).
- Phase-tag inventories (`[DOING]`, `[DONE]`, `[TODO]`, `[GATE]`,
`[USER-RESERVED]`, `[USER-APPROVAL GATE]`, `[EXEC-SHAPE]`)
byte-identical to
  `origin/main` — no tag advanced.
- The 3d bullet is re-wrapped as a whole; the text after "Wave 1:" is
proven
**word-identical** to `origin/main` by token-level comparison, so the
reflow
  changed wrapping only.
- Wrap: no added line exceeds 81 chars, inside the file's existing
distribution.

Load-bearing facts re-derived by command before writing:
`standards-ref`'s
`main` default and the `plan` job checkout (the trigger-coupling
discriminator);
`sync.yml`'s `push`/`workflow_dispatch`/`schedule` triggers; ruleset
`19388547`'s
`target`, `conditions.ref_name`, `bypass_actors`, and context string;
the
`requires-security-review` schema and its regenerated true-set; the
repo-level `rulesets` response shape; and the GitHub REST documentation
for the
installation-repository endpoints.

Two independent fresh-context verifiers audited this work with rationale
withheld. The second one is what surfaced the ordering error and four of
the
five other findings; every one of its blocking findings is addressed
here.

## Related

No linked issue — corrects the specification document for the in-flight
claude-review-lanes effort. Follows up #294.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant