Commit Graph

11 Commits

Author SHA1 Message Date
b4fcba7cca Take the job's outcome as an argument, not as an assumption
`report_job_log.py` hardcoded the word *failed* into the comment header. Every
caller guards the step with `if: failure()`, so it was true by construction --
until an unguarded probe in weblib-viewer#10 ran it on a job that passed, and
the successful run posted a comment reading as a failure report. Anyone
scrolling that PR would conclude the probe had failed.

The interesting uses of this script are exactly the ones that want
`if: always()`: a probe, or a job whose *output* is the point rather than its
exit code. Those all lied in the header.

`--status` now supplies the outcome. **The default is `failed`**, which is what
`if: failure()` means, so the four consuming repos are untouched -- a change in
required arguments would have broken all of them at once, since they take this
script from `@main`. `JOB_STATUS` in the environment does the same, matching how
every other argument here already reads its default from the Actions
environment.

`${{ job.status }}` yields `success`/`failure`/`cancelled`/`skipped` while a
human writing the flag reaches for `passed`/`failed`, so both spellings are
accepted and the expression can be passed straight through. An **unrecognised**
status goes into the header verbatim rather than being rejected: `argparse`'s
`choices=` would exit 2 on a value the table has not heard of, and the log --
the whole reason this script exists -- would never be posted. A reporter must
not become the thing that reports nothing.

The "no log file" note was status-dependent too; it claimed the step "failed
before the build started" regardless.

## Verified

`test_report_job_log.py`, new here: stdlib only and offline, posting to an
`http.server` on localhost that keeps what it is sent, so each test reads the
comment back. An exit status of 0 proves nothing -- the script deliberately
swallows HTTP errors so a failure to report cannot mask the failure being
reported. 21 tests, 0 skipped, 1.2s. Three of them drive the CLI as a
subprocess with only environment variables set, the way a workflow does.

Each check was shown to fire by injecting the fault and reverting it:

| injected fault | result |
|---|---|
| header hardcodes `failed` again (the original bug) | 10 failures |
| `DEFAULT_STATUS = "passed"` (would break the four callers) | 8 failures |
| unknown status raises, as `choices=` would | 2 errors |
| missing-log note keeps the failure wording | 1 failure |
| a stray `%` in the `--status` help text | 1 failure |

All five reverted; the file's checksum matches the pre-injection copy.

Also drops a tracked `__pycache__/report_job_log.cpython-313.pyc` and adds a
`.gitignore`. It was committed by accident in 061d8b2 and importing the module
from the tests rewrites it, so it would otherwise show up in every future diff
as stale bytecode of a file that had already changed.

Closes #8

Co-authored-by: bit <bit@das-labor.org>
2026-09-08 07:54:27 +00:00
be73235c01 Merge pull request 'README: say what the reconciler actually does now' (#6) from docs/reconciler-behaviour into main 2026-09-07 16:42:26 +00:00
67b133a115 README: say what the reconciler actually does now
Two behaviours landed in #4 that the one-line description did not mention, and
both are the kind of thing someone debugging would want to know before reading
the source:

  * the label is resolved from the repo *or the organisation* - labels moved to
    the org on 2026-09-07, and resolving from the repo alone is what silently
    turned the whole script into a no-op
  * an issue marked Status/On Hold or Status/Abandoned is left entirely alone,
    neither labelled nor unlabelled

Co-authored-by: bit <bit@das-labor.org>
2026-09-07 16:15:21 +00:00
259bdd93ca Merge pull request 'Resolve labels from the org, and never touch a held issue' (#4) from fix/org-labels-and-hands-off into main 2026-09-07 16:10:21 +00:00
e6f18b5a3b Resolve labels from the org, and never touch a held issue
Two fixes. The first is a live regression I caused today; the second stops one
before it arms.

1. `label_id()` looked the label up in `repos/{repo}/labels` only. Labels moved
   to the organisation today (weblib-archive#63) and that endpoint now returns
   `[]` in all five repos, so it returned None everywhere and the whole script
   became a silent no-op:

       $ sync_blocked_label.py --repo weblib/weblib-archive --dry-run
         skipped: no 'Status/Blocked' label in this repo
       --> 0 change(s)

   It failed *safe* - skipping rather than mislabelling, which is what that
   docstring was written for - but a job that runs every 15 minutes reported
   success while doing nothing. It now tries the repo, then the org, so it does
   not care how an instance is arranged.

2. bit, 2026-09-07: "the reconciler must not touch issues that are already on
   hold or abandoned". Implemented literally - neither add nor remove.

   This matters because `Status/*` is becoming exclusive again. Under that,
   adding `Status/Blocked` does not sit beside an existing status, it
   *replaces* it - so the reconciler would silently delete a deliberate
   `Status/On Hold` on its next pass. And since On Hold is exactly what makes
   the backlog sweep skip an issue, a parked issue would quietly become an
   available one, with nothing in the log to say why.

Verified against the live forge rather than by reading:

  * add path, org-resolved: stripped Status/Blocked off cfbypass#8, dry-run
    said "would add", the real run added it back
  * hands-off, as a control on ONE issue with ONE open blocker, changing only
    the label:
        without Status/On Hold ->  "would add Status/Blocked ... (blocked by #54)"
        with    Status/On Hold ->  "hands off", 0 changes, label intact

Closes #3

Co-authored-by: bit <bit@das-labor.org>
2026-09-07 16:07:18 +00:00
3a0d8db656 Merge pull request 'Stop the reporter naming a path that no longer exists' (#2) from fix/reporter-self-reference into main 2026-09-07 12:44:27 +00:00
f9777ccbd2 Stop the reporter naming a path that no longer exists
The comment it posts said "posted by `tools/report_job_log.py`". Since
weblib-archive#44 there is no such file in any consuming repo -- the script
lives here. So it pointed a reader at a path they cannot find, on the one
occasion they are already looking for the cause of a failure.

Links here instead.

Found by deliberately breaking a build on weblib-fs#31 to prove the failure
path worked. A green run never renders this message, so nothing else would
have surfaced it.

Co-authored-by: bit <bit@das-labor.org>
2026-09-07 12:15:05 +00:00
bit
9c26afd8d2 Merge pull request 'Record how to consume this, measured' (#1) from docs/measured-usage into main 2026-09-07 11:39:33 +00:00
d41e4ca2c6 Record how to consume this, measured
The seed commit said `uses:` did not work. That was measured while this repo
was private and is no longer true: with the full URL it works, and the action's
outputs.path reaches the caller and can run the tools.

The bare owner/repo form still fails - it resolves against the instance default
actions URL, not this host - so the README says which to use and why, with the
per-form results.

Co-authored-by: bit <bit@das-labor.org>
2026-09-07 11:30:00 +00:00
061d8b266c Seed the shared CI tools
Split out of the four repos per weblib-archive#44. All three files were
byte-identical across every repo at this moment, which will not stay true --
they converged only because four twin PRs landed within hours today, and
report_job_log.py had already drifted once before that.

Taken from weblib-archive, verified identical to every other copy first:

  with-nixpkgs.sh       ca43fa20  (cfbypass, archive, fs)
  report_job_log.py     aaef8f62  (cfbypass, archive)
  sync_blocked_label.py e6ddb21d  (all four)

action.yml is included so the `uses:` question can be re-measured now the repo
is public; it did not work while private.

Co-authored-by: bit <bit@das-labor.org>
2026-09-07 11:25:33 +00:00
bit
fb388546c5 Initial commit 2026-09-07 10:54:05 +00:00