diff --git a/.engine/knowledge/graph.json b/.engine/knowledge/graph.json index 8a5b530..ff1a381 100644 --- a/.engine/knowledge/graph.json +++ b/.engine/knowledge/graph.json @@ -4053,7 +4053,7 @@ }, "slug": "bootstrap", "source": { - "fingerprint": "sha256:8ed699ea3a47457c7d930b80bd03f8502756f0b0b2bf26ed43d5c02b83cdf591", + "fingerprint": "sha256:d1cde224ea91db72daebf5f2ea97ddc6624001c224e10256a8ec577328f76676", "path": ".engine/tools/bootstrap.py" }, "status": "active", @@ -4529,7 +4529,7 @@ }, "slug": "disposition_issue_resolution_check", "source": { - "fingerprint": "sha256:bcf048e4b39cb22dd4628be313dbb00f9318edc0eecaa4b06ecca0d58b40c312", + "fingerprint": "sha256:92063029a9dc55cfd29652079effbaaf9b0079539cfbe76b9dd0eeac0569a065", "path": ".engine/tools/disposition_issue_resolution_check.py" }, "status": "active", @@ -4801,7 +4801,7 @@ }, "slug": "issue_conformance_ci", "source": { - "fingerprint": "sha256:34dc46cb824356e75ef386cc3ecb38c30ac51da794b311eef41f177777d14037", + "fingerprint": "sha256:1175a7e114bf86167d422d8bdcdc8830c6c703525d80fca3d2244bb97aba0730", "path": ".engine/tools/issue_conformance_ci.py" }, "status": "active", @@ -5022,7 +5022,7 @@ }, "slug": "milestone_emit", "source": { - "fingerprint": "sha256:9d3717c2831dbc9087a9c7a43188bea6dfeb4a0b9a10868f8e5c2079b931fe8f", + "fingerprint": "sha256:2a03cc45baecc55a08088ee2af54b680f502b220f95fbbc6f24fe2249b40a9ce", "path": ".engine/tools/milestone_emit.py" }, "status": "active", @@ -5192,7 +5192,7 @@ }, "slug": "projects_sync", "source": { - "fingerprint": "sha256:0fb7fc0b5256efa42a90f715ef5f5bba9ec1122ed9daf8b048582b45f10ce312", + "fingerprint": "sha256:bef0404e623dd4339b369669c5424dd3f42f0c9af8f97fc9b4088706a9203be9", "path": ".engine/tools/projects_sync/projects_sync.py" }, "status": "active", @@ -5277,7 +5277,7 @@ }, "slug": "review", "source": { - "fingerprint": "sha256:a613a3030cc3a1b3c845d924fd8483e5eb13dc3c208563d7b479016d4255f87a", + "fingerprint": "sha256:1a586c2b2493e76023df23e8e652afe2db9ef01ca0148cc1882e51c2d9076ac3", "path": ".engine/tools/dependency_discipline/review.py" }, "status": "active", @@ -5447,7 +5447,7 @@ }, "slug": "spec_referent", "source": { - "fingerprint": "sha256:c2e06b463c577c4701ea40c7aa75fda9803202d6beb67bd324735c8859bdf95a", + "fingerprint": "sha256:a1a142f4a56a6c8c599c5ce931cab0d2cca53034396ec6e038adfe06c8ccd888", "path": ".engine/tools/spec_referent.py" }, "status": "active", diff --git a/.engine/tools/bootstrap.py b/.engine/tools/bootstrap.py index 264bbc2..6596a0c 100644 --- a/.engine/tools/bootstrap.py +++ b/.engine/tools/bootstrap.py @@ -65,11 +65,11 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import validate # noqa: E402 +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) import boot # noqa: E402 (repo_slug, gh_token, PROTECTED_BRANCH — the shared GitHub-context helpers) import protection_guard # noqa: E402 (REQUIRED_CHECKS + missing_floor — the SINGLE home of the floor) import telemetry # noqa: E402 (GitHubIssues.ensure_label — the slice-18 minimal ensure this inherits) -API_ROOT = "https://api.github.com" USER_AGENT = "engine-control-plane-bootstrap" TEMPLATE_PATH = os.path.join(validate.ENGINE_DIR, "templates", "control-plane-bootstrap.md") @@ -523,16 +523,7 @@ def __init__(self, repo: str, token: str, transport=None, refresh_fn=None, issue def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/dependency_discipline/review.py b/.engine/tools/dependency_discipline/review.py index f1eaeea..1b31629 100644 --- a/.engine/tools/dependency_discipline/review.py +++ b/.engine/tools/dependency_discipline/review.py @@ -59,8 +59,8 @@ sys.path.insert(0, _PARENT) import validate # noqa: E402 — the finding.v1 helper + ROOT (test-redirectable) +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) -API_ROOT = "https://api.github.com" USER_AGENT = "engine-dependency-review" _PRICING_URL = "https://github.com/pricing" _ENGINE_PREFIX = ".engine/" # the §13 wall: manifests here are the engine's own tooling, never product deps @@ -126,16 +126,7 @@ def __init__(self, repo: str, token: str, *, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/disposition_issue_resolution_check.py b/.engine/tools/disposition_issue_resolution_check.py index 6a34e9e..d07a69a 100644 --- a/.engine/tools/disposition_issue_resolution_check.py +++ b/.engine/tools/disposition_issue_resolution_check.py @@ -38,10 +38,10 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import validate # noqa: E402 (finding, section_blocks, get_pr_body, env_override_path, read) +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) ENGINE_LABEL = "engine" _CITATION_RE = re.compile(r"#(\d+)") -_API_ROOT = "https://api.github.com" _USER_AGENT = "engine-disposition-issue-resolution" @@ -61,15 +61,7 @@ def __init__(self, repo: str, token: str, *, transport=None): self._transport = transport or self._http def _http(self, method: str, path: str): - req = urllib.request.Request( - _API_ROOT + path, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": _USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=_USER_AGENT, method=method) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/issue_conformance_ci.py b/.engine/tools/issue_conformance_ci.py index 30c6574..648a714 100644 --- a/.engine/tools/issue_conformance_ci.py +++ b/.engine/tools/issue_conformance_ci.py @@ -32,10 +32,11 @@ and the in-session gate is the first line — widening the trigger would diverge from the locked design. SELF-CONTAINED TRANSPORT. telemetry.GitHubIssues has no per-Issue label/comment operations (its label is baked -in at construction and it only opens/updates engine-health Issues), so this tool carries its own small client -mirroring the audit_digest._http / telemetry._http idiom (same headers, 30s timeout, (status, json) return, -injectable `_transport` seam) over the per-Issue label and comment operations it needs (label ensure/add/remove, -comment list/post). telemetry.py is left untouched; the markers are single-sourced from issue_gate. +in at construction and it only opens/updates engine-health Issues), so this tool carries its own transport — its +own urlopen + (status, json) return + injectable `_transport` seam (30s timeout), building requests through the +shared `github_client` (the audit_digest / telemetry idiom) — over the per-Issue label and comment operations it +needs (label ensure/add/remove, comment list/post). telemetry.py is left untouched; the markers are +single-sourced from issue_gate. CLI (operator-runnable, falsifiable — the live net is what the workflow invokes): uv run --directory .engine -- python tools/issue_conformance_ci.py demo # scripted, fake GitHub, self-checks @@ -51,9 +52,9 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import issue_author # noqa: E402 +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) import issue_gate # noqa: E402 -API_ROOT = "https://api.github.com" USER_AGENT = "engine-issue-conformance" # The label applied to a non-conforming engine Issue. The design names the signal `needs-reauthoring` @@ -89,16 +90,7 @@ def __init__(self, repo: str, token: str, *, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/milestone_emit.py b/.engine/tools/milestone_emit.py index 857ab66..3e6e40e 100644 --- a/.engine/tools/milestone_emit.py +++ b/.engine/tools/milestone_emit.py @@ -12,11 +12,9 @@ SELF-CONTAINED ON PURPOSE. This is a CORE tool, but the build order is authored by the OPTIONAL product-design module. So it imports NO product-design code — a required tool must not depend on an optional module, or it would crash the "absent a build order, plan the phase yourself" path on every repo that never installed product-design. -It therefore carries its own minimal pipe-table parser (a knowing duplicate of a trivial parse) and its own -GitHub boundary, mirroring the engine's injectable-transport seam (`telemetry.GitHubIssues` / -`standing_situation`). The gh-client duplication across telemetry (issues) / weakening_guard + audit_digest + -lock_integrity (contents) / here (milestones) is the shared-client consolidation tracked in engine-template -#295 — folded there, not solved here. +It therefore carries its own minimal pipe-table parser (a knowing duplicate of a trivial parse); its GitHub +boundary builds requests through the shared `github_client` (the gh-client consolidation engine-template #295 +began) while keeping its own injectable-transport seam (`telemetry.GitHubIssues` / `standing_situation`). Idempotency (the `gh api` Milestones surface has no upsert): list every existing milestone (`state=all`, so a CLOSED phase is not recreated) paginated to exhaustion, match by trimmed title, and POST only the missing ones. @@ -35,9 +33,11 @@ import urllib.error import urllib.request +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # the sibling tools dir, for github_client +import github_client # noqa: E402 — the shared authenticated GitHub API client; request-build + # ---- constants ------------------------------------------------------------- -API_ROOT = "https://api.github.com" USER_AGENT = "engine-milestone-emit" # Where the committed build order lives (the product-design module's artifact), relative to the repo root. @@ -109,8 +109,7 @@ class GitHubMilestones: """The Milestone boundary. Mirrors the engine's injectable-transport seam (`telemetry.GitHubIssues` / `standing_situation`): `transport(method, path, body) -> (status, json)` is injectable, so the demo and tests fake ONLY the network and run the real list-then-create logic. A read/write failure RAISES (never a - partial "done"). Carries its own minimal `_http` — a knowing duplicate of the engine's gh client, tracked - for consolidation in #295.""" + partial "done"). Its `_http` builds requests through the shared `github_client`, keeping its own status/error handling.""" def __init__(self, repo: str, token: str, transport=None): self.repo = repo @@ -119,16 +118,7 @@ def __init__(self, repo: str, token: str, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/projects_sync/projects_sync.py b/.engine/tools/projects_sync/projects_sync.py index 490bc63..ce313d4 100644 --- a/.engine/tools/projects_sync/projects_sync.py +++ b/.engine/tools/projects_sync/projects_sync.py @@ -52,10 +52,10 @@ import boot # noqa: E402 — the already-resolved, leak-guard-clean signal assembler + gh token/slug import validate # noqa: E402 — ROOT (test-redirectable) for the gitignored config path import telemetry # noqa: E402 — ENGINE_DOMAIN_LABEL + the open-engine-item lister +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) # ---- constants --------------------------------------------------------------------------------- -API_ROOT = "https://api.github.com" GRAPHQL_PATH = "/graphql" USER_AGENT = "engine-github-projects-sync" @@ -162,16 +162,7 @@ def __init__(self, token: str, *, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/spec_referent.py b/.engine/tools/spec_referent.py index 5b1df02..d8c2a08 100644 --- a/.engine/tools/spec_referent.py +++ b/.engine/tools/spec_referent.py @@ -23,9 +23,9 @@ SELF-CONTAINED ON PURPOSE. This is a CORE tool, but the spec corpus is authored by the OPTIONAL product-design module. So it imports NO product-design code — a required tool must not depend on an optional module, or it would crash on every repo that never installed product-design. It carries its own minimal markdown parser (a knowing -duplicate of the trivial bits of `product_design/spec_form.py`) and its own GitHub boundary, mirroring the -engine's injectable-transport seam (`milestone_emit` / `standing_situation`). The gh-client duplication is the -shared-client consolidation tracked in engine-template #295 — folded there, not solved here. +duplicate of the trivial bits of `product_design/spec_form.py`); its GitHub boundary builds requests through the +shared `github_client` (the gh-client consolidation engine-template #295 began) while keeping its own +injectable-transport seam (`milestone_emit` / `standing_situation`). Fail-closed (the load-bearing safety): the work-item body is the one REMOTE read. A read FAILURE (HTTP >= 400, a null body, an unreachable host, an unexpected shape) RAISES `SpecReferentError` — it is NEVER read as "no spec -> @@ -54,9 +54,11 @@ import urllib.error import urllib.request +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # the sibling tools dir, for github_client +import github_client # noqa: E402 — the shared authenticated GitHub API client; request-build + # ---- constants ------------------------------------------------------------- -API_ROOT = "https://api.github.com" USER_AGENT = "engine-spec-referent" # The committed product spec tree, relative to the repository root. @@ -378,7 +380,7 @@ class GitHubIssues: """The Issue-read boundary. Mirrors the engine's injectable-transport seam (`milestone_emit.GitHubMilestones` / `standing_situation`): `transport(method, path, body) -> (status, json)` is injectable, so the demo and tests fake ONLY the network and run the real resolution. A read failure RAISES `SpecReferentError` (never a - silent no-op). Carries its own minimal `_http` — a knowing duplicate tracked for consolidation in #295.""" + silent no-op). Its `_http` builds the request through the shared `github_client`, keeping its own status/error handling.""" def __init__(self, repo: str, token: str, transport=None): self.repo = repo @@ -387,16 +389,7 @@ def __init__(self, repo: str, token: str, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8")