Skip to content

Each forge reads its own deliveries and connect fields; one journal follow for logs and events - #51

Merged
Bobowski merged 1 commit into
mainfrom
forges-and-journal
Nov 2, 2026
Merged

Bobowski merged 1 commit into
mainfrom
forges-and-journal

Conversation

@Bobowski

@Bobowski Bobowski commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Intent

Let each forge own its own differences, and let the journal adapter own the follow process. Source: the user, in the Yard audit. This pull request stacks on #50.

Found

  • origin_hook and github_hook in api.py each read the body, parsed the JSON, found the repo, logged hook.received, and scheduled a check. Only the signature and the body shape differ.
  • forge_put had one branch for each forge, with isinstance checks. Each branch validated its fields, called connect, sent the same journal event, and built the app answer.
  • The error "is not connected. Run yard forge setup" was written three times, in apps.py and two times in repos.py.
  • _stream_journal (with and without a filter) and _stream_events each ran the journalctl follow process, read lines, and killed the process in a finally.
  • logs_get had three read paths: JSON, text without a filter, and text with a filter.
  • _deploy_row in the events had its own copy of the exit-code rule from One plan builds the compose project and its image; the deploy record is a dataclass #49.

Changed

  • Forge gets signed, delivery, connect_with, app_info, require_connected, and the attributes event_header, signature_header, and connect_fields. Origin and GitHub fill them in. HooksPage._hook is the one handler for both routes. forge_put validates connect_fields and has no isinstance checks.
  • journal.follow_entries(proc, tick) yields the entries of a follow process, and None at each tick with no new line. It kills the process when it closes. Both streams use it in contextlib.aclosing. journal.follow still starts the process before the headers go out, so a missing journal is still a 502.
  • logs_get reads the entries once, applies the filter once, then writes JSON or text. _logs_page is gone.
  • _deploy_row uses status.deploy_status and status.deploy_error.

The webhook body shapes moved from api.py into forges.py. forges.py already reads the other forge-specific answers (installations, tokens, field names), so a delivery body fits there.

Not done

  • The legacy installation state in Forge.installations stays. A later pull request removes it with the 0.10 migration.

Proof

$ just check
...
501 passed in 61.14s

The hook tests in tests/server/test_api.py (status codes, a bad signature, a push, an installation event) and the log and event tests in tests/server/test_journal.py pass without a change.

Questions

  • An Origin delivery with broken JSON now sets last_delivery before the 400. Before, only a delivery that parsed set it. GitHub already set it before the parse.

🤖 Generated with Claude Code

@Bobowski
Bobowski changed the base branch from status-and-podman to main November 2, 2026 22:55
…ollow for logs and events

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Bobowski
Bobowski merged commit 8e39bb0 into main Nov 2, 2026
1 check passed
@Bobowski
Bobowski deleted the forges-and-journal branch November 2, 2026 22:56
Bobowski added a commit that referenced this pull request Nov 2, 2026
…main.py (#52)

## Intent

Split the 1,684-line `app/cli/main.py` along its seams, and send every
HTTP call through one function. Source: the user, in the Yard audit.
This pull request stacks on #51.

## Found

- `_api_try`, `_stream`, `_follow_events`, and `_follow_deploy` each
repeated the `urlopen`, `HTTPError`, `TimeoutError`, and `URLError`
branches. The copies had drifted apart: `_follow_events` had no
`TimeoutError` branch.
- The pattern "print the raw answer for `--json` or for a wrong shape,
else print the table" was copied in eight commands.
- `_checkout_pair` and `_app_name` ran the same three git calls.
`_read_pem` and `_ask_key` both read and checked a PEM. The GitHub `PUT`
body was built in two places.
- The manifest handler read its server state with `getattr`, but
`_ManifestServer` declares those fields.
- Small leftovers in `render.py`: `_ago` was an alias of `ago`,
`_name_when` had one caller, and the deploy block wrapped one record in
a list (from the old `deploys` field).

## Changed

- `app/cli/client.py` holds the login (`load`), the calls (`api`,
`api_try`, `stream`, `open_stream`), the flags (`out.json`,
`out.no_color`), and `die`, `fail`, `ask`, `color_on`, and `show`.
`_urlopen` is the one place that opens a request and turns a connection
failure into the "did not answer" message.
- `app/cli/checkout.py` holds `checkout()` (top, repo, branch),
`remote_repo`, `app_name`, and the name helpers.
- `app/cli/forge.py` holds `yard forge setup`, with one `_pem_at` and
one `_put_github`.
- `main.py` keeps the commands and the argument tree: 958 lines.
- The other modules call `client.api_try` through the module. Thus a
test patches one name. `tests/cli/test_main.py` has
`_fake_api(monkeypatch, fake)` in place of the patch, `_forge_rows`, and
`_env` lines it repeated 34 times.
- `AGENTS.md` names the new modules.

## Not done

- I kept most `isinstance` guards on the answers from the server.
Without them, an older server can make the CLI raise a traceback in
place of printing the raw answer.
- I did not convert the remaining hand-written fakes in `test_main.py`
to a table of routes. Many of them assert on the request body or on the
order of the calls, and a table hides that.
- `_align` and `_render` in `render.py` stay separate: one has a header
and an `(none)` line, the other has neither.

## Proof

```
$ just check
...
501 passed in 63.19s
```

The CLI tests run unchanged in what they assert. Only their patch
targets changed (`client.api_try`, `client.out.json`,
`forge.SETUP_POLL`, and so on).

## Questions

None

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant