You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Facts port: one snapshot of a pull request from GitHub, with its completeness flags #191
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".
A fixture of a timeline read short, in the form of trials/P-29.truncated.json: complete.timeline is set from pageInfo.hasNextPage alone, never from timelineItems.totalCount, which counts every item whatever itemTypes asked (P-33, from "Design: what P-33 and P-34 showed of closing references, an issue's content time and a timeline's count", Design: what P-33 and P-34 showed of closing references, an issue's content time and a timeline's count #240).
A contract test replays every trials/ fixture through the port and compares its answer with the fake GitHub's for the same exchange (L-51's "comparing the fake's answers with a fixture is waiting on adapters/"); a response the fixture shows GitHub rejecting fails the test (FR-56).
tools/check_purity.py still passes: kernel/ imports nothing from adapters/. adapters/AGENTS.md carries the rules of that place only, each naming its check.
Every Boundary row and lesson waiting on adapters/ that this port holds is re-pointed to a named test; the rest (the records port's, the Consumer entry's) name a narrower wait. tools/check_context.py passes.
Needs. Slice 0's exit. The reader App's key is stored; a test never reads it (recorded responses only).
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;
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.
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.
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.
Activity
don-petry commentedon Oct 21, 2026
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:
schemas/facts.schema.json(23 required fields, eightcompleteflags);Passread, and P-33;sim/world.py(the fake buildsfacts/1directly, in the Kernel's shapes) andsim/AGENTS.md's row that the fake answering in GitHub's shapes waits onadapters/;adapters/, across 18 files;tools/check_context.py(_arrived: "the first directory of the path decides");tools/check_repo.py(LOCALhas noadapters;urllibpasses as a first name, sourllib.requestpasses, whiledocs/standards/security-boundaries.mdsays "refused");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/1comes 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'slast_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_arrivedreads a wait on a file inside a planned directory, such asadapters/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 inadapters/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.pyrefusesurllib.request,http,socket,sslandftplibeverywhere but one file,adapters/github.py, which holds the one transport. This closes the hole whereurllib.requestpassed asurllib.adaptersjoinsLOCAL.docs/standards/security-boundaries.md's "Nothing is fetched" gains its exception in one sentence, held by the same check.api.github.comalone 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.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.
Passshape (state,is_draft,author,class,targets_protected,mergeable, and the title beside them, never in them);last_pusherfrom the head branch's activity;diff,content_idandcomplete.diff;changed_pathsandcomplete.changed_pathsadapters/github.py,adapters/pull.pylast_pusherand class rows (0012, 0015, 0018, 0022, and 0026 twice); 0026's activity row; 0039's title row; 0020's paths row, except for its renameadapters/timeline.pyadapters/facts.py, which assemblesfacts/1with everycompleteflag andread_atand 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 groundsadapters/checks.py,adapters/facts.pysim/AGENTS.md's rowRows outside this story name the file that will hold them:
adapters/records.py;adapters/acceptance.py;adapters/issues.py;adapters/settings.py;adapters/effects.py;controller/.5. What PR 1's reads hold, rule by rule.
last_pusheris 0026's read exactly: the newest activity entry's actor when itsafteris the head, else null. The App is read by its bot login withactor.typeBot.classcomes from the author, from whether the head is in another repo, and from the configuration's App identities. A fork is alwaysoutside, anddependencyis given only for an exact App identity. Held against P-28 and P-28.app.diffis checked against the same read's counts (0002). A 406 istruncated, and a difference between the diff and the counts iscomplete.difffalse. No read is built frompatch. Held against P-29.base and P-29.truncated.content_idissha256:over the diff of the head against its merge base. It must equal the hash P-29.base recorded.changed_pathscomes from REST/pulls/N/filesread in full. It stops at 3,000 files, where page 31 comes back as a 200 with nothing in it, and is thencomplete.changed_pathsfalse (P-29.truncated).previousisprevious_filename. No fixture yet holds a rename, so the rename test is marked@pendingand 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.Passread. That fixture showsauthorAssociationdiffers by the token asking (L-57), so PR 1 reads nocan_write.6. Tests and mutants.
tests/test_adapters.pyreplays each fixture's exchanges through the replay transport and asserts each part against what the fixture shows.tests/test_tools.pygains ruling 2's and ruling 3's cases.adapters/pull.pyand of the two checks gets a mutant intools/mutants.py, naming its test and checked by the capped filtered run, at most 40 for each path.adapters/gets anAGENTS.mdof 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.pyand 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.
Facts port: a cut-short answer is refused, a replay holds the order a…
check_repo: http joins the standard library the transport uses, and s…
Facts port: a retarget is a new read, a planned name keeps its dots, …
Mutants: the four for the last fixes, and the head mutant brought up …
Facts port: the one transport, and a pull request's own facts, last p…
don-petry commentedon Oct 21, 2026
The Maintainer's answer (2026-10-21, relayed by the orchestration session): ruling 3 is accepted.
adapters/github.pyalone may reach the network, by HTTPS toapi.github.comonly.tools/check_repo.pyrefusesurllib.request,http,socketandssleverywhere 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.L-57: what each credential is shown of a permission is a pending test…
Facts port: the pending permission test asks for the reader App's rea…
don-petry commentedon Oct 21, 2026
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:/pulls/{n}/filesfilesrenamed, withprevious_filenameRENAMED, no old pathadded, no sourceADDEDmodifiedMODIFIEDSo only REST's
previous_filenamenames a renamed file's old path, and GitHub listed the copy as a plain addition.adapters/pull.py'schanged_pathsalready reads REST. A port that read GraphQL'sfileswould lose the old name of a protected file renamed away. Thecopiedstatus 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'sSERVEDcap reads, anda_run_list_stops_shortis now grounded by it.