Give the annia enqueuer a source tree and an image #102

Merged
tepichord merged 13 commits from feat/100-annia-enqueuer-source-tree-image into main 2026-09-25 17:06:47 -07:00
Owner

Closes #100

Automated PR created by forgejo-done adapter.

Closes #100 Automated PR created by forgejo-done adapter.
The receiver annia-infra #310 was supposed to build: POST /api/hook on 8080,
a bounded raw-body read before any parse, HMAC-SHA256 verified against
FORGEJO_WEBHOOK_SECRET (sha256= and the bare-hex forge headers, never the
sha1 X-Hub-Signature), and one {repo, kind, number, event_id} pointer handed
to a single owner thread for exchange annia.events with routing key milner —
publish, never declare. No secret or a wrong signature is a 401 and no
message; pika is imported where it is used so the module graph loads without
a broker client.

Refs: #100
enqueuerSrc, enqueuerApp, enqueuerCheck and enqueuerImage beside the router's
three. The interpreter is python313.withPackages (ps: [ ps.pika ]) rather
than two bare contents entries — pika beside the interpreter is off its
sys.path, so the pod would start and every signed delivery would die on
ModuleNotFoundError — and the client comes from the flake's nixpkgs pin, a
store path, never a pip install. enqueuerCheck asks the image's own layers
for the Cmd and PYTHONPATH its config declares, the /app/enqueuer/*.py links,
an interpreter link that resolves inside the layers (it cannot equal
pkgs.python313: withPackages builds an environment), the store path
import pika reads, and the module graph imported from the extracted root.

Refs: #100
ci/enqueuer-image.sh reads the Deployment's image: line — the one extractor
build-enqueuer and the guard share, so they cannot disagree about which
revision main runs. ci/enqueuer-tag-guard.sh keeps the router's both-directions
form over the widened set (enqueuer, flake.nix, flake.lock) compared against
the base branch's tip and never a merge base — pipeline #67 is the failure
encoded there — and refuses the <annia-infra-pin> placeholder on HEAD. The
set is exactly build-enqueuer's path filter; tests execute both directions,
the flake.lock-bump-without-a-bump case, the introducing change and the
shallow workspace in throwaway repositories.

Refs: #100
build-enqueuer is build-reaper's shape with the guard's compared set as its
path filter (enqueuer/**, flake.nix, flake.lock on both events plus manual):
both halves build ./#enqueuerImage and ./#enqueuerCheck and cat the check,
only a push to main publishes — the pull-request half pushes nothing, because
no preview runs the enqueuer and a per-PR tag would have no consumer — and
the push verifies the registry serves the digest it just pushed. The
enqueuer-tag-guard step runs it unprivileged on pull requests. tests/test_static.py
gains both scripts in the shellcheck list, the flake_steps entry, the fourth
tag-guard test (including the filter/guard set agreement), the image-closure
test, and the new step in the lists that hold the builds' shared shape.

Refs: #100
image: zot.faun-snapper.ts.net/milner/annia-enqueuer:0.1.0, and
deployment.yaml listed in the kustomization — one change, because the listing
is what puts the placeholder test onto the rendered layer and the real tag is
what keeps apps.yaml's wait: true honest. The comment block that explained
why the manifest was deliberately unlisted (annia-infra #310 never built the
image) now states the truth: this repo builds it, and the guard holds the pin
to the source. Port, env and probes untouched — they were already the
contract.

Refs: #100
set-credential.sh's two steps, docs/rabbitmq.md's registration bullet and its
closing paragraph, and the placeholder test's docstring all said annia-infra
#310 builds and publishes this image. None of it was ever true (#310 is the
runtime/workspace image; annia-infra #308 names Milner as the owner), so the
runbook now says what is: this repo's enqueuer/ and build-enqueuer publish
the pointer to annia.events with routing key milner, declare nothing, pin
0.1.0 listed in the kustomization as one change, and webhook registration is
a Forgejo setting at annia.vando.dev/api/hook waiting on #88's route.
docs/rabbitmq.md:139 and set-credential.sh's neighbouring-runbooks line stay:
both correctly name #308 as the *consumer* contract.

Refs: #100
docs/nix.md's §14 rows enumerate the image builders and guards, §15's header
named three, §15a described the family without its newest member and the
reaper was the cheapest of three — all four now include the enqueuer, with
the withPackages/pika-from-the-pin note (the stdlib-only rule bends because
no AMQP client exists in the standard library) and the sentence that 0.1.0 is
a counter of the image's revisions, not a compatibility claim. AGENTS.md's
repo map gains the enqueuer/ row and the fourth ci/ pair; ARCHITECTURE.md's
images section gains the receiver and its build; docs/storage.md,
OPERATIONS.md and docs/woodpecker-ci.md's step lists name build-enqueuer and
enqueuer-tag-guard where they counted the old set.

Refs: #100
Re-wrap three spliced docstrings in tests/ and pad the new AGENTS.md
`enqueuer/` row to the table's column width.

Refs: #100
Every verb but `POST /api/hook` and every wrong path answers a 404 without
consuming the declared body. HTTP/1.1 keeps the connection alive for the next
request, so those bytes were parsed back as a request line — a body of
`GET /smuggled ...` reached the handler as a second request. The POST
wrong-path branch already closed for exactly this reason; the other verbs did
not.

`_not_found` now closes, and `_respond` stops writing a body for HEAD, whose
response is a GET's headers and no body. The tests hold both: the verb loop
asserts one response per connection, and the HEAD test asserts nothing follows
the header block. The two untested `_read_body` refusals get their cases too —
a non-numeric `Content-Length` is the missing-length 411, and a body that
stopped mid-way is the 400 it answers with.
docs(agents): keep the repo-map row for the image family in one cell
All checks were successful
ci/woodpecker/pr/gates Pipeline was successful
97d6e26e20
The enqueuer sentence was appended after a stray `|`, which added a third cell
to a two-column table — and a renderer drops extra cells, so the sentence
describing the receiver's pika-carrying interpreter never rendered.
tepichord added this to the MVP project 2026-09-25 16:35:11 -07:00
Three textual conflicts, all in shared prose/tooling rows that both sides
extended, and one semantic conflict the merge created.

AGENTS.md — the `ci/` repo-map row. main added `notify-build.sh` (story 23);
this branch added the `enqueuer-image.sh` / `enqueuer-tag-guard.sh` pair.
Union: `notify-build.sh` before `router-image.sh`, the enqueuer pair after
the reaper pair.

docs/nix.md §14c — the mount table's dev-shell row. main added the `notify`
step and `python3` to the realised tools; this branch added
`enqueuer-tag-guard`. Union of both.

tests/test_static.py — main replaced the hand-maintained shellcheck list with
`git ls-files '*.sh'` discovery, for the reason its docstring gives (the two
credential scripts landed unlinted). Discovery covers every tracked script,
so it supersedes this branch's two added entries; took main's side.

Semantic conflict, fixed in the following commit: main also widened
`test_python_lint` from `tests/` to `git ls-files '*.py'`, so this branch's
`enqueuer/app.py` is linted for the first time and fails ruff twice
(TRY004 at `pointer_for`'s non-dict refusal, S110 at `_wake`'s hand-off).
fix(enqueuer): satisfy the widened python lint, which now reads this tree
Some checks failed
ci/woodpecker/pr/gates Pipeline failed
d2bc599344
main changed `test_python_lint` from `ruff check tests/` to
`git ls-files '*.py'`, so merging main made `enqueuer/app.py` linted for the
first time. It fails two rules, both of them heuristics that are wrong here.

`pointer_for` refuses a payload that is a list, a string or a number with
`ValueError`, and the handler catches `ValueError` to answer 400. Ruff's
TRY004 wants `TypeError` for the `isinstance(payload, dict)` branch, but
"valid JSON that is not an object" is malformed input and not a caller
mistake, and `TypeError` is not a `ValueError` subclass — raising it would
escape the handler and answer 500 to a sender that only sent a list. Keep
`ValueError` and suppress the rule with that reason.

`_wake`'s `try/except/pass` already carried a `BLE001` suppression and a
comment: a callback hand-off must not fail the delivery that triggered it,
and the pointer is queued either way. Add `S110` to the same suppression
rather than logging, for the reason the comment implies — during a broker
outage this fires once per delivery, and a line each would bury the owner
thread's own failure log.
chore(reaper): bump the tag to 0.2.1, because the flake pin moved again
All checks were successful
ci/woodpecker/pr/gates Pipeline was successful
ci/woodpecker/pull_request_closed/cleanup Pipeline was successful
fe6aec117d
`reaper-tag-guard` failed pipeline 119 (pull_request, d2bc599): the branch
changes `flake.nix` (the enqueuer's `enqueuerImage`/`enqueuerCheck` and its
`enqueuerPython` binding), so the guard sees the pin move, and both sides had
independently bumped the tag `0.1.0 -> 0.2.0` — main for the IRC change, this
branch for its own flake.nix addition. Identical edits on both sides merge
clean, which is exactly why the invariant is now false relative to main: after
the merge `before == after == 0.2.0` while `flake.nix` still differs.

Same fix, and the same reason, as `1d6a330` on main: the reaper's image is
built from the flake pin, its tag is what the kubelet keys on with
`IfNotPresent`, and a pin that moves under a tag that does not is the silent
failure the guard exists to refuse. `0.2.1` is the counter's next value.

`flake.lock` is untouched, so `reaperImage`'s bytes are unchanged and this is a
re-publish — the direction the guard deliberately allows, and the reason
`build-reaper`'s main-push filter names the manifest as well as the two flake
files. The pull-request half builds and checks it and pushes nothing.
tepichord deleted branch feat/100-annia-enqueuer-source-tree-image 2026-09-25 17:06:47 -07:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
tepichord/milner!102
No description provided.