The shims that dispatch a Run: run.yml, approve.yml and queue.yml, each held by its template and its hash (record 0157)
This adds the three shims that dispatch a Run: run.yml, approve.yml and queue.yml. Each is held word for word by its template and by its hash. This is PR A of issue 196, "Workflows: the frozen entry shim, the poller, the Run's three jobs and the nudge, each held to its shape", as the Factory Builder ruled it on 2026-11-07. The poller's shim is PR B, which comes next.
Nothing goes live here. All three run only on workflow_dispatch, and nothing dispatches them yet, because the poller is not on main. Each if: refuses three things:
- any dispatcher but the account
41898282, which the pass's own token runs as. Every workflow token here runs as that account too: what keeps a dispatch the pass's is that only the jobspass,wakeandlaneare grantedactions: write; - a re-run;
- a Pause.
queue.yml is also never dispatched while config/controller.json names no queue App, under decision 0128.
The Pause. The repo's own FACTORY_PAUSED reads true, set by the Maintainer and read back, so nothing that merges here can start a Run.
Every file under .github/ here is a protected path, so each one is the Maintainer's to approve: approve.yml, queue.yml and run.yml.
The shims
| File | Calls, at @factory/v0-next | Template | Hash in SHIMS |
|---|---|---|---|
run.yml (sf-run) | run-jobs.yml | RUN_SHIM, which already existed | e0888dab… |
approve.yml (sf-approve) | approve-job.yml | APPROVE_SHIM, new | de636e91… |
queue.yml (sf-queue) | queue-job.yml | QUEUE_SHIM, new | f29c8cac… |
What every shim has in common:
- three required inputs:
name,itemandpass; run-name: ${{ inputs.name }};- one job,
lane, grantedactions: writeandcontents: read. Its act'swakeneeds the write, and a called job gets no more than its caller grants (record 0115); - the concurrency group
${{ inputs.item }}, which cancels nothing; secrets: inherit;- the same
if:.
The called files are all in factory/v0.1.0, which is factory/v0-next at 499835d5. Each is held word for word, and main changes none of them.
What the check changes (tools/check_workflows.py)
LANE_SHIMSmapsrun.yml,approve.ymlandqueue.ymlto their templates.lane_shim_problemsreplacesrun_problemsand holds each file to its template, its dispatcher being the accountconfig/controller.jsonnames.- A shim that dispatches a Run may name its run. Before, only
run.ymlcould. The records port reads a Run by its run-name, so an act's Run named by its workflow alone would stay unidentified for ever, and under decision 0036 every admission would defer. Record 0128 also names each queue act by its run-name. No other shim may name its run. CALLERS: no file butapprove.ymlcallsapprove-job.yml, and none butqueue.ymlcallsqueue-job.yml, just as none butrun.ymlcallsrun-jobs.yml.- The
lanerule widens: a job that calls an act's job also holds its item's lane. SHIMSholds the three hashes.
Records
- 0157, the number the Factory Builder gave it: "An act's shim is the Run's but for its name and the file it calls". It widens record 0057's row "Its shape", where only
run.ymlnames its run. - 0036's row waited on
run.yml. It now names the Run's shim test, the act-shim test and the live-shim test, with their mutants. Its half on the pass's own run still waits onpoller.yml, PR B. - 0055's row waited on
approve.yml. It now namestest_the_approving_act_holds_the_approvers_token_aloneandtest_the_shims_that_dispatch_a_run_stand_as_their_templates_and_their_hashes, with their mutants.
Checks
| Check | Result |
|---|---|
tests.test_tools.Workflows | 51 OK |
make verify, on an export of 6e7d966, then of 41d66ab, with the record numbered 0157 in the export only | Pass, both |
The 34 new and changed shim mutants, at 41d66ab | 34 of 34 killed |
make verify, on an export of the numbered head c1b640e | 1659 tests OK (expected failures=8) |
Reads
All four reads were of 6e7d966, before the fix round.
| Reviewer | Head | Blocking | Major | Minor | Info |
|---|---|---|---|---|---|
| claude-review | 6e7d966 | 0 | 0 | 1 | 2 |
| claude-security | 6e7d966 | 0 | 0 | 0 | 1 |
| agy (Argon) | 6e7d966 | 0 | 6 | 2 | 0 |
| astra, in Muse's place | 6e7d966 | 0 | 0 | 0 | 0 |
Muse was not run: it is out of quota until 2026-11-09T00:00Z.
| Finding | From | Fixed in 41d66ab |
|---|---|---|
The shims' if: admits the account every workflow's own token here runs as, not the pass's alone | claude-security | The record, the shims standard, the check and each shim's comment say what holds it: only pass, wake and lane are granted actions: write, and the act's step reads the item again. The hashes are recorded again |
0036's re-pointed row names a test of run.yml alone, and no test of the live shims | claude-review, agy | It names the act-shim and live-shim tests and their mutants. Its half on the pass's own run waits on poller.yml |
The grant paragraph leaves out queue.yml and queue-job.yml | claude-review | Named |
| An act's shim is never compared with the Run's beyond five lines | agy | It is asserted equal to RUN_SHIM line for line, but for its name and the file it calls. New mutant: "an approval's name may be left out" |
LANE_SHIMS is never asserted to hold the three | agy | Asserted |
No mutant puts back if name == "run.yml": | agy | "only run.yml is held to its shim" |
No mutant for the queue act's re-run, its wake's write, its file, the approving act's lane, or APPROVE_JOB in CALLERS | agy | A mutant for each, all killed |
| No mutant disables the hash comparison | agy | No change: "a shim is held by nothing" already does, killed by test_a_shim_is_held_word_for_word |
| The record is unnumbered | claude-review, agy | 0157, in c1b640e |
The three CodeQL notes, "unpinned tag", flag the Channel tag that a shim calls by design (decision 0006). The tag rulesets guard it (decisions 0115 and 0124). Each thread was answered and resolved. Whether to dismiss the alerts is the Maintainer's call.
🤖 Generated with Claude Code