Facts port: the one transport, and a pull request's own facts, last pusher, diff and paths (#191)
The facts port's first pull request, the first commit of adapters/, built by the working ruling posted on #191 (rulings 1 to 6). Part of #191; it does not close it. Two more pull requests follow: the timeline's reads, then checks and the one snapshot.
What it adds.
adapters/github.py, the one transport.Livesends HTTPS toapi.github.comalone, by a path from the API's root. It follows no redirect, uses no proxy and revalidates every request.- It sets a timeout on each socket operation, reads each answer in chunks up to a bound, and takes its token from its caller.
- A body that is not JSON is decoded without loss, so a hash is over the bytes GitHub sent.
Replayanswers from a trial's fixture, each recorded request once, matched by method, path, Accept and query, and refuses anything else (L-51).
adapters/pull.py, a pull request's facts from its own reads.- The pull request itself, read by REST, with the title handed beside the facts.
class, from the caller's identities by account id and type. The Factory's App and a dependency bot must be Bots, and the Maintainer a User. A fork, or anyone not named, isoutside.targets_protected, from the base branch.last_pusher, from the newest activity entry when itsafteris the head (decision 0026).diffandcontent_id, counted inside hunks and held to the pull request's counts. A 406too_largeistruncated, read whole, with the head commit's content id (decision 0002).changed_paths, read in full, short at the 3,000-file cap (P-29).same_head, a re-read that refuses a head which moved while the parts were read.
tests/test_adapters.py. It replays P-28, P-28.app, P-29.base and P-29.truncated, and holds every part to what GitHub said. The rename test is@pendinguntil P-43's fixture lands (#250, ruling 2).
Two checks change.
tools/check_repo.pyrefuses an import of what reaches the network everywhere but the transport:urllib.request,http,socket,ssland the like, by name, with an alias or from its package. Todayurllib.requestpasses asurllib, though the standard says it is refused. The rule is a net: another name,importlibor a subprocess passes it.docs/standards/security-boundaries.mdand the record say so.tools/check_context.pylets a directory built in parts list its files still to come in itsAGENTS.md. A wait on one of those files holds until the file exists, and a wait on any other file there has arrived, as before.- Without this, the first file in
adapters/fails all 58 rows waiting on it. - Each of the 58 now names the test in this pull request that holds it, or the file in
adapters/(orcontroller/) that will.
- Without this, the first file in
One protected path. config/hooks.json gains adapters/ as an input of the verify Lens. tests/test_tools.py requires every package at the top of the repo to be one. A change to a Lens's inputs is the Maintainer's to review.
Departures from the ruling.
- The core fields come from REST
GET /pulls/N, not from a GraphQL read in P-32's shape. The fixtures hold REST reads of one pull request; the snapshot's one query comes with PR 3. classtakes identities from its caller, since the configuration names none yet. It fails closed.
Record. Decision 0044, "The ports are the one place that reaches the network, through one transport held to GitHub's API", numbered as the next free one at this pull request's opening.
Mutants. 50 in tools/mutants.py, each naming its tests and each checked by hand on a scratch copy, all killed. Per path: 16 on adapters/github.py, 28 on adapters/pull.py, 3 on tools/check_repo.py and 3 on tools/check_context.py, all under the filtered run's cap of 40.
Reviews, one fix round on 794b2ec: a fresh-context Claude review, a security pass, and AGY on Flash, whose first run produced nothing because it tried a tool. "Muse: not run" (Maintainer, 2026-10-20: Kernel, sim and hooks only); this pull request's one line in sim/AGENTS.md re-points a wait.
Taken:
- The diff was hashed after a lossy decode, so two diffs could share a content id (both reviews; blocking in the Claude review).
- Lines of content beginning
+++or---were dropped from the counts (all three). dependencywas given to a User, against the rows that say an exact App identity (Claude).- A login can be re-registered after a rename, so accounts are now matched by id (security).
- A push between reads could pair one head with another's content id (both).
- A 406 other than
too_large, or one with no head sha, was read as truncated (Claude, security). - Branch names were put unescaped in paths (both).
- Proxies were taken from the environment, a redirect's response was left open, network errors escaped as other exceptions, and 64 MiB was allocated per read (Claude, security).
- The standard and the record claimed more than the import check holds (both).
- "No test reaches the network" had no check, and the claim is dropped (security).
read()had no test (AGY).- An exact multiple of 100 files was untested (Claude).
Not taken:
- A deadline on the whole request, past the per-operation timeout (security, nit). It is left to the Controller's pass, which bounds a pass's time.
- Tying each planned file to an issue number (security).
tools/check_context.pyrefuses an issue number without its quoted title in anAGENTS.md, so the plan names the files alone. Each story's ruling names the files it builds. - A pull request whose head is null reaching
last_pusher(AGY).read()refuses one with no head commit, so none reaches it.
Codex on eb30ced, four threads, all taken:
- An answer cut short (
IncompleteRead), on a body or on an error's, escaped as another exception. It is now refused. - A replay matched a GraphQL request by its query alone, and in any order. It now matches the whole body, variables among it, in the order recorded.
- A file row with a status GitHub does not give, or an old name on anything but a move, was read as an ordinary change. It now makes the list short.
httpjoins the standard-library list for the transport's framing errors. The network rule still refuses it everywhere else.
The export verify passed whole on 04c8f02.
🤖 Generated with Claude Code
Summary by CodeRabbit
-
New Features
- Added GitHub pull-request data retrieval, including author classification, repository ownership, branch protection, latest pusher, diff statistics, and changed-file tracking.
- Added safeguards for incomplete, malformed, oversized, truncated, or inconsistent GitHub responses.
- Added deterministic replay support for GitHub interactions, enabling reliable offline operation and testing.
-
Documentation
- Documented GitHub integration boundaries, adapter responsibilities, response completeness rules, and security constraints.
-
Tests
- Expanded coverage for transport validation, pull-request parsing, diffs, renames, pagination, and network-access controls.