Facts port: who acted and with what access, every label event, every review with its dismissal, and the acts since a writer (#191)

Merged
DO
4d ago
+623-11

Part of #191: the facts port's second pull request, the timeline. It builds on the transport and adapters/pull.py, which #264 landed.

What it adds: adapters/timeline.py.

  • Accounts. An actor's kind comes from GitHub's account type and the configuration the caller gives, by account id, never from a name's spelling (decision 0005, L-57).
    • machine_user is the User named as the approver, and no other.
    • lens is a Bot named as a Lens's App, read as that Lens's name.
    • Any other Bot is app, and any other User is human.
    • A User's write access, the machine user's among them, is read from the collaborator permission. admin, maintain and write count; triage does not, as the Maintainer ruled on #222. It is read once a login for the snapshot. An App is never asked.
    • These are refused, never guessed: a User whose login GitHub would not give, [bot] among them; a permission GitHub does not give; and a 404 on the permission.
  • The timeline and the reviews. Both are read page by page, following the link header. A list is whole only when its last page names no next page, never by a page's length (§3.3). An item that is not an object is refused.
  • label_events. As the timeline lists them, with who made each and when.
  • reviews.
    • Each review comes with its commit and that commit's content id: the sha256 of the compare diff against the base's head. P-29 shows that this is the pull request's own content.
    • A commit too large to diff, or one no longer there, takes the commit's own id, as adapters/pull.py does for the head.
    • From the timeline: who dismissed each review and what it was before.
    • No text of GitHub's is kept: no body, no dismissal message, no comment.
  • since_writer.
    • It gives when a person with write access last acted, and how many acts the Factory made since: the machine user's approvals, a dismissed one included; and the machine user's or a Factory App's labels and queue moves (decision 0003).
    • A mention or a subscription is nobody's act.
    • An action an App took with a person's token is not that person's.
    • An act in the same second as a writer's action is counted, the reading that stops work.
  • Tests: tests/test_adapters.py, class Timeline. They replay P-7's reviews, dismissals and timeline, P-29's compare, and P-28. A stand-in answers the permission and compare reads no fixture holds yet; the permission's trial is the one #222 plans.

Not here: threads. They need GraphQL for their resolved state, and their category join reads the Lens's applied artifact, which hits the same redirect as #192's outcome read. The four thread rows and 0038's join wait on adapters/threads.py, which is now in adapters/AGENTS.md's plan.

Rows.

  • 0003's acts row, 0005's account row and L-57 name the tests that hold them.
  • 0005's row says a thread's accounts wait on adapters/threads.py.
  • L-57's row says what each credential is shown is recorded by #222's trial.

Mutants. 31 on adapters/timeline.py, each naming its tests, each checked to compile and killed by hand. An equivalent one was dropped: a 404's body names no permission, so the permission check refuses it anyway.

Reviews, one fix round on 2ff5469: a fresh-context Claude review, a security pass, and AGY on Flash, which approved with one finding: a mutant that broke the syntax instead of the rule. "Muse: not run".

Taken:

  • The machine user was read as unable to write (Claude; blocking). kernel/derive.py's standing approval needs can_write, so its approval could never stand. It is now read like a person's, and matched by id (both).
  • A dismissed approval fell out of the act count (both; blocking). The timeline gives a review's state now, so the re-approval loop the ceiling bounds went uncounted.
  • A list was whole at a short page (Claude; blocking). Section 3.3 forbids that; GitHub's link header names the next page.
  • Any App's label or queue move counted as the Factory's (both).
  • An act in the writer's second was not counted (both).
  • A review's 406 or lost commit refused the whole snapshot (Claude).
  • An item that is not an object was skipped (Claude).
  • A 404 on the permission read as none (security). That fails open for a renamed writer.
  • An App's action with a person's token counted as that person's (security).
  • A login with dots or a slash reached the path (security).
  • Three mutants were killed for unrelated reasons, and one broke the syntax (Claude, AGY).
  • 0005's and L-57's rows claimed more than their tests hold (Claude).

Not taken:

  • That the queue events' names are ungrounded (security). No fixture holds added_to_merge_queue on a REST timeline. P-2 (#185) records the queue, and its fixture then grounds the names or changes them.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added pull-request timeline and review processing.
    • Added validation for actor identities, permissions, review states, labels, timestamps, and timeline data.
    • Added support for pagination, review dismissals, commit comparisons, writer activity, and machine-user approval tracking.
    • Write access now depends on authenticated collaborator permissions.
  • Tests

    • Expanded coverage for timeline processing, reviews, permissions, pagination, actor classification, and invalid responses.
    • Added mutation testing coverage for key validation and attribution rules.
  • Documentation

    • Updated implementation references and decision records to reflect the new timeline support.