Skip to content

Facts port: one snapshot of a pull request from GitHub, with its completeness flags #191

Open

Description

@don-petry

Part of the epic "Epic: slice 1, first approval. A Release on next reviews a pull request to this repo, and the machine user's approval stands".

Why. The first commit of adapters/. Every decision from 0001 on has rows waiting on it (49 in all), and the Kernel has never been given facts that GitHub produced. Slice 1's critical path item 8: "the two ports, with completeness flags".

Done when

Needs. Slice 0's exit. The reader App's key is stored; a test never reads it (recorded responses only).

Design. §2.2 facts port; §3.3; §9.1 item 8; decisions 0001, 0003, 0005, 0020, 0025, 0026; L-50 to L-53, L-57.

Activity

  1. don-petry commented on Oct 21, 2026

    @don-petry
    ContributorAuthor

    Working rulings (overturnable in review)

    Solution Architect, delegated. On main at ab53e4f. Read from the issue, and from "Port: the paths a pull request changes, with both names of a rename" (#138), folded in here.

    Also read:

    • the slice-1 epic's Order ("After slice 0's exit: the facts port, then the records port, lane a") and the plan review's slice-1 story 6;
    • design §2.2 (the facts port: "One snapshot of a Work item from GitHub", may not "Return text to the Kernel") and §3.3, field by field;
    • schemas/facts.schema.json (23 required fields, eight complete flags);
    • the fixtures that hold GitHub's shapes for a pull request: P-1, P-7, P-24, P-28, P-28.app, P-29.base, P-29.truncated, P-32 and P-8's Pass read, and P-33;
    • sim/world.py (the fake builds facts/1 directly, in the Kernel's shapes) and sim/AGENTS.md's row that the fake answering in GitHub's shapes waits on adapters/;
    • the 58 rows and lessons waiting on adapters/, across 18 files;
    • tools/check_context.py (_arrived: "the first directory of the path decides");
    • tools/check_repo.py (LOCAL has no adapters; urllib passes as a first name, so urllib.request passes, while docs/standards/security-boundaries.md says "refused");
    • decisions 0002, 0005, 0012, 0020, 0026 and 0039, and lessons L-51, L-52, L-53 and L-57.

    What this story decides. How the Factory's first I/O code is shaped, and what its first pull request holds. The whole of facts/1 comes from reviews, threads, checks, the timeline, the queue and acceptance, as well as the pull request itself. That is too much for one review, and half of it rests on trials not yet run (P-41, P-42, P-43). So the story lands in three pull requests, each re-pointing the rows it holds and naming a narrower wait for the rest.

    1. It can start now. The epic puts the facts port after slice 0's exit, but nothing it reads waits on that exit. Its schema (facts/1, 0039), its fixtures (batch 1) and its rules (0026's last_pusher, 0002's diff) are all on main. The exit's open items (#8, #24, #62, #89, #95) touch none of its files. Why: the order was set to keep lanes apart while slice 0 finished, not because the port needs the exit. If the Maintainer wants it held, it waits; nothing else is blocked by that.

    2. A wait can be narrower than a directory. tools/check_context.py's _arrived reads a wait on a file inside a planned directory, such as adapters/records.py, as arrived only when that file exists. A wait on the directory itself, adapters/, arrives when anything is in it, as today. The change has a test each way and a mutant. Why: without it, the first file in adapters/ fails all 58 rows at once. Each port story could then neither land alone nor say which story holds which row. The epic's rule ("re-point to a named test or to a narrower wait") has no narrower wait to point to until this exists.

    3. Only adapters/ may reach the network, and only GitHub's API.

    • tools/check_repo.py refuses urllib.request, http, socket, ssl and ftplib everywhere but one file, adapters/github.py, which holds the one transport. This closes the hole where urllib.request passed as urllib. adapters joins LOCAL.
    • docs/standards/security-boundaries.md's "Nothing is fetched" gains its exception in one sentence, held by the same check.
    • The transport sends HTTPS requests to api.github.com alone and follows no redirect off it. It sets a timeout on every request, takes its token as an argument and never reads the environment. It logs nothing of a body.
    • The transport has a replay twin for tests. The twin answers each request from a fixture's exchanges, matched by method, path and GraphQL query, and refuses any request the fixture does not hold. It never answers with an empty default (L-51).
      Why: the required check still fetches nothing. The port is the only code the design lets touch GitHub, and holding that to one file makes it reviewable.

    4. Three pull requests.

    PR Holds Module Rows re-pointed to a test
    1 Ruling 2's change; ruling 3's transport and its check; the pull request's own fields from one GraphQL read of P-32's Pass shape (state, is_draft, author, class, targets_protected, mergeable, and the title beside them, never in them); last_pusher from the head branch's activity; diff, content_id and complete.diff; changed_paths and complete.changed_paths adapters/github.py, adapters/pull.py 0002's diff row; 0005's account kinds; the six last_pusher and class rows (0012, 0015, 0018, 0022, and 0026 twice); 0026's activity row; 0039's title row; 0020's paths row, except for its rename
    2 Reviews, threads and their category joined from the Lens's applied artifact; the timeline's acts and label events, each actor with its kind adapters/timeline.py 0003's acts; the four thread rows; 0038's join; L-57
    3 Checks with their exact name, App and workflow path, and the queue; the one snapshot, adapters/facts.py, which assembles facts/1 with every complete flag and read_at and is validated against the schema; the contract test that replays every fixture holding a pull request's shape and compares the port's answer with the fake's for the rule each fixture grounds adapters/checks.py, adapters/facts.py The Lens, check-run, rerun and workflow-path rows; L-53's list of one item; sim/AGENTS.md's row

    Rows outside this story name the file that will hold them:

    • the records port's rows: adapters/records.py;
    • acceptance, which is slice 2: adapters/acceptance.py;
    • the issue's rows on 0028: adapters/issues.py;
    • the repo's settings (0001): adapters/settings.py;
    • a Role's write tried on the canary (L-50): adapters/effects.py;
    • the budget (L-52) and the Pause variable (L-58): controller/.

    5. What PR 1's reads hold, rule by rule.

    • last_pusher is 0026's read exactly: the newest activity entry's actor when its after is the head, else null. The App is read by its bot login with actor.type Bot. class comes from the author, from whether the head is in another repo, and from the configuration's App identities. A fork is always outside, and dependency is given only for an exact App identity. Held against P-28 and P-28.app.
    • diff is checked against the same read's counts (0002). A 406 is truncated, and a difference between the diff and the counts is complete.diff false. No read is built from patch. Held against P-29.base and P-29.truncated.
    • content_id is sha256: over the diff of the head against its merge base. It must equal the hash P-29.base recorded.
    • changed_paths comes from REST /pulls/N/files read in full. It stops at 3,000 files, where page 31 comes back as a 200 with nothing in it, and is then complete.changed_paths false (P-29.truncated). previous is previous_filename. No fixture yet holds a rename, so the rename test is marked @pending and its row says "not held yet" until P-43's fixture (Trials P-41, P-42 and P-43: reviews, labels and check runs on a pull request, and the lists read short #250, ruling 2) lands. Then it passes unchanged, or the rule changes by its own story.
    • The core fields come from P-32's Pass read. That fixture shows authorAssociation differs by the token asking (L-57), so PR 1 reads no can_write.

    6. Tests and mutants.

    • tests/test_adapters.py replays each fixture's exchanges through the replay transport and asserts each part against what the fixture shows.
    • tests/test_tools.py gains ruling 2's and ruling 3's cases.
    • Each rule of adapters/pull.py and of the two checks gets a mutant in tools/mutants.py, naming its test and checked by the capped filtered run, at most 40 for each path.
    • adapters/ gets an AGENTS.md of its own: one table, its last column "Held by".

    7. Record. One decision, numbered at enqueue: "The ports are the one place that reaches the network, through one transport held to GitHub's API". Its table holds rulings 2, 3 and 4.

    8. Gates. Export verify; a fresh Claude review and a security pass. The transport is a new trust boundary, so the security pass reads it first. AGY on Flash: it is the first port, and it imports the Kernel's schemas. "Muse: not run", since this is not Kernel, sim or hooks. The capped filtered mutant run on adapters/pull.py and the two checks.

    9. Out of scope. The token's mint, which is the identity port's (#194). Calling the port from a pass, which is the Controller's (#193). A live read in any test.

    Needs the Maintainer? No, for building it.

    • One scope point is his to overturn: ruling 1 starts the port before slice 0's exit.
    • Ruling 3 adds an exception to a security standard, so the security pass and the record carry it to his review.
  2. don-petry commented on Oct 21, 2026

    @don-petry
    ContributorAuthor

    The Maintainer's answer (2026-10-21, relayed by the orchestration session): ruling 3 is accepted. adapters/github.py alone may reach the network, by HTTPS to api.github.com only. tools/check_repo.py refuses urllib.request, http, socket and ssl everywhere else. It is decision 0044, which merged with PR #264 at d78cee5. Ruling 1 is answered too: the ports go on the critical path ahead of #8's last step.

  3. don-petry commented on Oct 21, 2026

    @don-petry
    ContributorAuthor

    For this story's PR 2, from trial P-43 (trials/P-43.json, merged in #274). Canary pull request 48 renamed one file, copied one unchanged and edited one:

    File REST /pulls/{n}/files GraphQL files
    the rename renamed, with previous_filename RENAMED, no old path
    the unchanged copy added, no source ADDED
    the edit modified MODIFIED

    So only REST's previous_filename names a renamed file's old path, and GitHub listed the copy as a plain addition. adapters/pull.py's changed_paths already reads REST. A port that read GraphQL's files would lose the old name of a protected file renamed away. The copied status is still handled, though P-43 did not see GitHub give it.

    The same trial found that a filtered run list stops at 1,000. Page 1 counts more, and page 1,001 is empty and counts 0. This is what adapters/records.py's SERVED cap reads, and a_run_list_stops_short is now grounded by it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions