Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .engine/knowledge/graph.json
Original file line number Diff line number Diff line change
Expand Up @@ -5413,7 +5413,7 @@
},
"slug": "engine.v1",
"source": {
"fingerprint": "sha256:de28e10e8b3bf43c5ed4b48fb1503f94aceb6a27405a513491b3d911475e8371",
"fingerprint": "sha256:dd84e6ea48f2945f68fe88bbf014c9313412831c7b5e2fe4c83e74f9747ecd3f",
"path": ".engine/schemas/engine.v1.json"
},
"status": "active",
Expand Down Expand Up @@ -6442,7 +6442,7 @@
},
"slug": "boot",
"source": {
"fingerprint": "sha256:c4c3adcb7aac76e7fc31eb429b93af71f597a2e741bda3b4ad30243f86060c08",
"fingerprint": "sha256:6a754ca0e3c0cd1fe487ea38af1398127c09b5b3af2ea603d4fe181dcd2976f4",
"path": ".engine/tools/boot.py"
},
"status": "active",
Expand Down Expand Up @@ -6507,6 +6507,7 @@
"imports": [
"tool:boot",
"tool:github_client",
"tool:moment",
"tool:protection_guard",
"tool:repo_identity",
"tool:telemetry",
Expand All @@ -6519,7 +6520,7 @@
},
"slug": "bootstrap",
"source": {
"fingerprint": "sha256:ff8d1d43e9a1b8b3cb9311f27c731c9ba45e655d31d5aa9d64f5d12a36fb0018",
"fingerprint": "sha256:a84ed2e2ba23c6031ffab48169ee778f9a11efcf43fe2060de9144622b9dce4e",
"path": ".engine/tools/bootstrap.py"
},
"status": "active",
Expand Down Expand Up @@ -9876,7 +9877,7 @@
},
"slug": "protection_guard",
"source": {
"fingerprint": "sha256:6a295eeb90182549d278f571c819beab648884d851a0da544a6c38f2e703e046",
"fingerprint": "sha256:af0bb70842fa9761110d2dc49a58b7213c6db700fd1200cfe53eeb73d994f2da",
"path": ".engine/tools/protection_guard.py"
},
"status": "active",
Expand Down Expand Up @@ -11048,7 +11049,7 @@
},
"slug": "test_boot",
"source": {
"fingerprint": "sha256:bce831aab02cb0e08ff2c99ea1292bf883543fef902b153f7ce13fddd7e7d447",
"fingerprint": "sha256:75f7a44224bbe899146dff7c7d7bd4ca82336608c052234254015486fff98b78",
"path": ".engine/tools/test_boot.py"
},
"status": "active",
Expand Down Expand Up @@ -11129,7 +11130,7 @@
},
"slug": "test_bootstrap",
"source": {
"fingerprint": "sha256:0a66d453ecbb331ad47bb2977ca5748b8944746e768ee78d193bb474bb529dcc",
"fingerprint": "sha256:bcd26ffc51c7690174c75972de868a6a278490724383b8aabfe688557dbea7c0",
"path": ".engine/tools/test_bootstrap.py"
},
"status": "active",
Expand Down Expand Up @@ -12709,7 +12710,7 @@
},
"slug": "test_module_manager",
"source": {
"fingerprint": "sha256:61b29b5b0ffb5554a4365523140a20c2a47ccc314468801d864dac6c2b2f11d5",
"fingerprint": "sha256:f525e7902646db4a3f924a77ebeed91ad172488c8cf8a3104762814e30b2ad8e",
"path": ".engine/tools/test_module_manager.py"
},
"status": "active",
Expand Down Expand Up @@ -13128,7 +13129,7 @@
},
"slug": "test_protection_guard",
"source": {
"fingerprint": "sha256:78a61b46043bb12c98da57fa104f77ba209e8367f909d7fc1ca6f0f790a6577b",
"fingerprint": "sha256:215ef9949af2f0a0036e1384af2bb94c5599f261330342355244f47a41feecdc",
"path": ".engine/tools/test_protection_guard.py"
},
"status": "active",
Expand Down
27 changes: 27 additions & 0 deletions .engine/schemas/engine.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,33 @@
}
}
},
"protection_posture": {
"description": "A deliberate, operator-consented record that this repository's GitHub plan cannot host branch-protection rulesets at all, so the standing protection check reports it as an honest, non-blocking warning instead of hard-failing every pull request. Written ONLY by `bootstrap.py accept-unprotected`, which first re-verifies that the branch-rules API genuinely returns GitHub's plan-limitation 403 for this repo — never inferred, never written silently. Its mere presence never softens the gate: the standing check ALSO demands a live plan-limitation 403 at evaluation time, so on any repo whose plan can host protection (the read succeeds there) this record is inert and the check stays hard. Absent on every deployment whose platform can host protection (the default) — and that absence is exactly what keeps the check hard-failing when protection SHOULD be available but is missing or unreadable. The `operator_login`/`recorded_on` fields are an advisory audit breadcrumb of who ran the accept and when — never authenticated proof of consent (a repository whose plan cannot host branch rulesets has no enforced review on the pull request that records this, so the field is a recorded 'by', not a verified one).",
"type": "object",
"additionalProperties": false,
"required": ["status", "reason", "operator_login", "recorded_on"],
"properties": {
"status": {
"description": "The recorded posture. unsupported-platform: the repository's GitHub plan cannot host branch rulesets (the branch-rules API returns GitHub's plan-limitation 403). An enum-of-one deliberately, leaving room for a future posture without reshaping the block.",
"enum": ["unsupported-platform"]
},
"reason": {
"description": "A short plain-language reason recorded with the acceptance, for the operator reading engine.json later (for example 'This private repository's plan does not expose branch rulesets; the owner accepted running without the protection floor.'). Never empty.",
"type": "string",
"minLength": 1
},
"operator_login": {
"description": "The GitHub login whose token recorded this decision, read from GET /user at record time — an advisory 'recorded by', the who of the acceptance. It is the token owner, NOT authenticated proof of the operator's consent (the reviewed pull request carrying this block is that); downstream copy presents it as 'recorded by', never as governance evidence.",
"type": "string",
"minLength": 1
},
"recorded_on": {
"description": "The UTC date the operator accepted the posture (YYYY-MM-DD), which the standing warning and boot's calm line cite. Recorded via the engine's sanctioned clock, not a hand-rolled time idiom.",
"type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
}
}
},
"engine_identity": {
"description": "The distinct, non-admin GitHub identity that authors the engine's commits and pull requests in TEAM mode, so the operator becomes the enforced code-owner reviewer — a change cannot merge without a second sign-off, and this identity (holding no admin) cannot itself weaken the ruleset. Recorded when the operator switches solo->team via the team-switch operation; absent in solo (the default) and in the engine's own home repository (which runs no first-run setup), so a manifest without it stays valid. Records only the PUBLIC identity — never a token or any secret, which stays in the operator's own credential store, so switching back to solo simply drops this record.",
"type": "object",
Expand Down
12 changes: 12 additions & 0 deletions .engine/templates/control-plane-bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ The authorization screen completed but the permission didn't save (some sign-in
Protection is still off, so work can merge unreviewed. Let's try once more, or sign in again first. I'll
keep reminding you until it's on.

## If it couldn't turn on — this plan can't host branch protection

I couldn't turn on branch protection — this repository's GitHub plan doesn't offer the branch-protection
rules the safety gate needs (private repositories need GitHub Pro, Team, or Enterprise; public repositories
have them for free). This isn't a permission problem — your account administers the repository fine.
Protection is not active, so work can merge unreviewed. Two ways forward: upgrade this repository's plan (or
make it public), then say **turn my safety gate back on** — or, if you're deliberately running without the
gate, say **accept that my plan can't protect this branch** and I'll record that, so the engine stops failing
every pull request over a limitation it can't fix and instead reports the gate as off by your informed choice.
Either way I do it for you — you never type a command yourself. Until then, I'll keep reminding you the gate
is off.

## Removing the engine — keep or remove your safety rule

I set up a safety rule on your main branch that requires checks to pass and a pull request before anything
Expand Down
78 changes: 62 additions & 16 deletions .engine/tools/boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import subprocess
import sys
import unicodedata
import urllib.error
import urllib.parse

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
Expand Down Expand Up @@ -288,19 +289,27 @@ def emit_refused_cursor_finding(*, spool_path: str | None = None) -> bool:
def protected_branch_signal(repo: str | None, token: str | None,
branch: str | None = None) -> tuple[str, str | None]:
"""The protected-branch governance signal, RELAYED from protection_guard (the control-plane's own
evaluation), in three honest states:
("off", reason) -> the gate is NOT in force: a pinned governance alarm that OFFERS the fix.
boot stays read-only and only offers; the assistant runs the already-built,
idempotent one-click `bootstrap.py finalize` (bootstrap.ControlPlane.finalize —
apply plus the workflows-present guard, so it can't re-deadlock a freshly-arrived
repo) on the operator's consent — the shared repair-offer contract
(boot-session-start.md).
("on", None) -> the gate fully bites: no alarm.
("unknown", None) -> boot could not verify it (no token/repo/unreachable): a clear degraded line
that must NEVER read as a green all-clear.
evaluation), in four honest states:
("off", reason) -> the gate is NOT in force: a pinned governance alarm that OFFERS the fix.
boot stays read-only and only offers; the assistant runs the already-built,
idempotent one-click `bootstrap.py finalize` (bootstrap.ControlPlane.finalize —
apply plus the workflows-present guard, so it can't re-deadlock a freshly-arrived
repo) on the operator's consent — the shared repair-offer contract
(boot-session-start.md).
("on", None) -> the gate fully bites: no alarm.
("unsupported", date) -> this repository's GitHub plan cannot host branch rulesets AND the operator
recorded a deliberate acceptance of that (protection_posture): a CALM,
non-alarm steady state, never "your gate is off (broken)" — the platform,
not a fault, is why the gate is off, and the operator already accepted it.
The second slot carries the accepted-on date. Requires BOTH the recorded
posture AND a live plan-limitation 403, so a stale/forged posture never
quiets the alarm on a repo whose plan can host protection.
("unknown", None) -> boot could not verify it (no token/repo/unreachable/an unrecognized failure):
a clear degraded line that must NEVER read as a green all-clear.
"""
if not repo or not token:
return "unknown", None
posture = protection_guard.recorded_posture() # an operator-consented plan-limitation acceptance, or None
# The branch to probe is the AUTHORITATIVE default (env -> recorded -> origin/HEAD -> "main"), resolved at
# call time so it self-heals a pre-recorded-key deployment; quoted so a malformed name can never redirect
# this token-bearing request off its `/rules/branches/` path.
Expand All @@ -316,6 +325,13 @@ def protected_branch_signal(repo: str | None, token: str | None,
# check enforces.
missing = protection_guard.missing_floor(
rules, protection_guard.REQUIRED_CHECKS, tier=protection_guard.resolve_tier())
except urllib.error.HTTPError as e:
# A recorded acceptance PLUS a live plan-limitation 403 is the calm off-by-acceptance state. Any other
# failure — no posture, or a 403 that isn't a genuine plan limit — stays the honest "unknown" degraded
# line, never a false all-clear.
if posture and protection_guard.http_error_forbids_rulesets(e):
return "unsupported", posture.get("recorded_on")
return "unknown", None
except Exception: # noqa: BLE001 — unreachable / auth / malformed body -> unknown, never a false "on"
return "unknown", None
if missing:
Expand Down Expand Up @@ -1757,12 +1773,26 @@ def render_dashboard(s: dict) -> str:
# Gated on the safety gate being ON (#810 usability): "complete" must never appear beside a "your gate is off"
# alarm — an un-gated repo has NOT finished setup. When the gate is off the confirmation is held back (and the
# marker is NOT cleared, in _relay_lines), so it fires on a later start once the gate is on.
# "unsupported" (this plan can't host protection, accepted by the operator) is ALSO a completed-setup state:
# setup landed, the gate simply couldn't be turned on for a reason the operator accepted. It gets the
# one-time confirmation too — with HONEST wording (never "your gate is protecting it") — so an
# unsupported deployment isn't stuck showing setup-incomplete forever, and its marker clears below the same
# way (avoiding the every-session loop). After this one-time line it stays calm and silent — no alarm.
setup_landed = s.get("setup_landed")
if setup_landed and setup_landed.get("present") and s.get("gate") == "on":
pinned.append(
"✅ **Setup is now complete.** Your setup changes have landed on your main branch, your safety gate "
"is protecting it, and your project is ready — that was the last onboarding step. From here it's "
"ordinary work.")
if setup_landed and setup_landed.get("present") and s.get("gate") in ("on", "unsupported"):
if s.get("gate") == "unsupported":
branch = s.get("protected_branch") or PROTECTED_BRANCH
pinned.append(
"✅ **Setup is now complete.** Your setup changes have landed on your main branch. Branch "
"protection isn't available on this repository's GitHub plan, and you accepted running without "
f"the safety gate on `{branch}` — so there was no gate to turn on, and that was the last "
"onboarding step. If you later upgrade the plan (or make the repository public), say **turn my "
"safety gate back on** and I'll enable it.")
else:
pinned.append(
"✅ **Setup is now complete.** Your setup changes have landed on your main branch, your safety "
"gate is protecting it, and your project is ready — that was the last onboarding step. From here "
"it's ordinary work.")

# The engine-MECHANIC setup OFFER (eADR-0026, Slice 3): this engine builds a separate OWNED product checkout,
# but this machine's path to that checkout is missing (the portable fork case — the committed slug travelled,
Expand Down Expand Up @@ -1830,6 +1860,13 @@ def render_dashboard(s: dict) -> str:
f"I couldn't verify your safety gate from here (no GitHub access), so **don't assume "
f"`{s.get('protected_branch') or PROTECTED_BRANCH}` is protected** — confirm it before merging "
f"anything important.")
elif s["gate"] == "unsupported":
# An accepted plan-limitation: the operator recorded that this repo's GitHub plan can't host branch
# protection. Deliberately NEITHER an alarm (the "off" branch) NOR the misleading "no GitHub access"
# degraded line (the "unknown" branch above) — it is a calm, accepted steady state, acknowledged once
# by the setup-complete confirmation above and otherwise silent here. Explicit so the state is handled,
# not left to fall through by accident.
pass

# Engine findings NO LONGER pin a ⚠ here. A routine finding count is the engine's own housekeeping (the
# operator's lowest priority in a deployed repo), so it renders only as a quiet facts line below and is
Expand Down Expand Up @@ -2421,6 +2458,8 @@ def present_marker_line(s: dict) -> str:
return "⚠ Your safety gate is off" # same noun as the dashboard + the unknown-gate marker below
if s["gate"] == "unknown":
return f"⚠ {PRESENT_MARKER}: couldn't verify the safety gate"
# "unsupported" is intentionally NOT a ⚠ here: an accepted plan-limitation is a calm steady state, so it
# falls through to the calm `▸ Project status` marker below rather than reading as a governance alarm.
if s["refused"]:
return f"⚠ {PRESENT_MARKER}: couldn't read where the project stands"
if s["strand"]: # ranked after the governance alarms; a governance alarm still wins the marker
Expand Down Expand Up @@ -2525,6 +2564,10 @@ def _pushed_alarms(s: dict) -> list:
f"{RELAY_MARKER} the safety gate couldn't be verified (no GitHub access), so they shouldn't "
f"assume `{s.get('protected_branch') or PROTECTED_BRANCH}` is protected — confirm before merging "
f"anything important.")})
elif s["gate"] == "unsupported":
# An accepted plan-limitation steady state — NOT a governance alarm, so nothing is pushed across
# sessions. Explicit (not a silent fall-through) so the intent is on the record.
pass
if s["refused"]:
alarms.append({"key": "refused", "value": True, "collapsible": False, "full": (
f"{RELAY_MARKER} the engine couldn't read where the project stands, so project status is "
Expand Down Expand Up @@ -2706,7 +2749,10 @@ def _relay_lines(s: dict) -> list:
# Clear only when the confirmation actually SHOWS (same gate-on condition render_dashboard uses), so a gate-off
# session holds the marker rather than burning the one-time confirmation before the operator ever sees it.
sl = s.get("setup_landed")
if sl and sl.get("present") and sl.get("main") and s.get("gate") == "on":
if sl and sl.get("present") and sl.get("main") and s.get("gate") in ("on", "unsupported"):
# "unsupported" clears the marker too: its one-time completion confirmation renders in the dashboard on
# this same condition (render_dashboard), so an accepted plan-limitation deployment finishes onboarding
# once and never loops the "setup landed, awaiting the gate" state.
first_run_health.clear_first_run_marker(sl["main"])
# The broken-hooksPath offer rides this SAME single decide() call (#707/#708), like off_main/foreign_license —
# it is NOT a pushed governance alarm (it renders only in the dashboard, at the top of the offer tier). It is
Expand Down
Loading
Loading