Skip to content

Records port: the Factory's own run records, unidentified Runs and the cache #192

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 second half of the Kernel's input. P-17 and P-22 showed how run records appear (#161, decision 0035), and the port is where that decision's adapters/ rows land.

Done when

  • adapters/records.py lists the Factory's own workflow's runs on the default branch started by a schedule or a dispatch (I-34), reads each with Cache-Control: no-cache and keeps the response's date and age for the pass record (0035 ruling 4), parses run-name/1 into the record's item, Intent, Role, content_id and run, and reports a counted uncompleted record still under the workflow's name as unidentified after one more read 5 s later (0035 rulings 1 and 3).
  • A list read short (1,000 runs, or a 200 with no data) is unreadable (L-53), and outcome.json is read only for a Run that did not succeed (decision 0029's row).
  • The contract test replays trials/P-17.json and trials/P-22.json through it and matches the fake's a_queued_run_is_listed, a_new_run_is_listed_late and a_new_run_is_listed_unnamed.
  • Every row waiting on adapters/ that this holds is re-pointed; tools/check_context.py passes.

Needs. The facts port's PR (shared adapters/ rules and test harness).

Design. §3.10; §9.1 item 8; decisions 0003, 0029, 0035; L-53.

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;
    • design §3.10 (the run-name, the unidentified window, the outcome read);
    • decision 0036, which holds the rules this issue cites as "0035": rulings 1 to 4, and its row waiting on the port;
    • decision 0029's row (the outcome of a Run that did not succeed), 0039's row (outcome/1 read only then), 0038's row (the records port parses run-name/1), and 0003's row (a record counts only if it is the Factory's own workflow, on the default branch, started by a schedule or a dispatch);
    • lesson L-53 (1,000 runs, or a 200 with no data);
    • trials/P-17.json and trials/P-22.json (the run list read as it came: cache-control: private, max-age=60, no request sent no-cache, no age, and every run named sf-trial …, none in run-name/1's form);
    • schemas/run-record.schema.json and schemas/run-name.schema.json;
    • tools/check_trials.py's HEADERS, which has no age;
    • the grounding rules a_queued_run_is_listed, a_new_run_is_listed_late and a_new_run_is_listed_unnamed;
    • this story's partner, the facts port's ruling on Facts port: one snapshot of a pull request from GitHub, with its completeness flags #191.

    What this story decides. What the second half of the Kernel's input is, read from GitHub: the Factory's run records, the ones still unnamed, and how stale the read was. The rules are already decided in 0036, and this story builds them.

    1. One pull request, after the facts port's first. It reuses adapters/github.py's transport and its replay twin, and adds adapters/records.py. It re-points every row that names adapters/records.py under the facts port's ruling 4: 0003's row, 0029's, 0036's, 0039's outcome row, and 0038's parse. Why: the transport is the one place that reaches the network (#191, ruling 3), so the records port waits for it and adds nothing to it.

    2. The list.

    • Read the runs of each Role's workflow and the poller's, on the default branch, filtered by event to schedule and workflow_dispatch (0003's row, I-34).
    • Send Cache-Control: no-cache on every read, and allow If-None-Match.
    • Keep each response's date and age for the pass record. Neither reaches the Kernel, and a non-zero age is a stale read (0036, ruling 4).
    • A list read short is unreadable, with complete.runs false: a total_count above what the pages returned, a 1,000th record with more behind it, or a 200 whose body has no runs where the count says there are some (L-53).

    3. The name and the unidentified window.

    • run-name/1 is parsed by a function in adapters/records.py into the record's item, Intent, Role, content_id and run. It is held by unit tests on examples/run-name.json and every counter-example of it, since no fixture holds a real Factory run-name. The first canary run the Controller names will, and its fixture then joins the contract test.
    • A counted record that is a workflow_dispatch of a Role's workflow, with an item in its input and a name not yet in run-name/1's form, completed or not, is held for one more read 5 s later. It is reported as unidentified by its GitHub id only if still unnamed then (0036, rulings 1 and 3).
    • The wait is the port's. Tests give it a clock and a sleeper, so nothing waits in a test.
    • A schedule's run, a pass's own run and an item-less run are never unidentified.

    4. The outcome. outcome/1 is read from a Run's artifact only for a Run whose conclusion is not success (0029, 0039). It is read as a zip through the same transport, from the Actions artifacts API, with its size bounded and its one member named. No fixture holds an artifact read, so that test is @pending and its row says "not held yet". It is held when the Controller's first canary Run records one.

    5. The fixtures. P-17 and P-22 are replayed for the list's shape, the late listing and the unnamed window, and matched to the three grounding rules. tools/check_trials.py's HEADERS gains age, date and the request's cache-control, so a fixture recorded by the port's own reads can keep them. Neither trial is re-run.

    6. Tests and mutants. tests/test_adapters.py gains the records port's cases. Each rule gets a mutant naming its test, checked by the capped filtered run on adapters/records.py.

    7. Record. None: 0036 decided all of it. Rows only.

    8. Gates. Export verify; a fresh Claude review and a security pass, which reads the zip read first. AGY on Flash. "Muse: not run". The capped filtered mutant run.

    9. Out of scope. Reading applied.json for a thread's category, which is the facts port's PR 2 and uses this port's artifact read. Dispatching a Run, which is the Controller's.

    Needs the Maintainer? No.

  2. don-petry commented on Oct 21, 2026

    @don-petry
    ContributorAuthor

    Reopened: #268's body said "It closes #192 except for the outcome read", and GitHub read the keyword. #268 built the records port (merged at c90bdc4), but the outcome read is still owed. A Run's outcome.json is an artifact, which GitHub serves by a 302 off api.github.com. Decision 0044's transport refuses that redirect, so the read waits on a decision to allow that one host, and then on adapters/artifacts.py. Until then, a list holding a failed Run is not whole. The remaining box is decision 0029's row, "The port reads the outcome of a Run that did not succeed", which is waiting on adapters/artifacts.py.

  3. don-petry commented on Oct 21, 2026

    @don-petry
    ContributorAuthor

    The Maintainer's answer (2026-10-21, in chat to the orchestration session): allow the one redirect. Decision 0044's transport may follow the artifact download's 302 only: from the artifact-download endpoint, only to GitHub's own artifact storage host, over HTTPS, with no auth header forwarded and the body size bounded; a row on 0044 and counter-examples in tools/check_repo.py. Built in adapters/artifacts.py, which completes this story's outcome read.

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