Repository navigation
Conversation
…is a dataclass Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bobowski
added a commit
that referenced
this pull request
Nov 2, 2026
…is a dataclass (#54) ## Intent Write each rule of a deploy once: the image of a plan, the Dockerfile path, the compose files, and the status of an exit code. Source: the user, in the Yard audit. It replaces #49, which GitHub closed when the base branch of #49 was deleted. #48 is merged. ## Found - `apps._ready_repo`, `apps._ready_source`, and `compose.saved_project` each built the image and the Dockerfile path with their own copy of the rules: `split('@')[0]@<digest>` two times, `compose:<rev[7:19]>` two times, and the `yard:` Dockerfile rule two times. `compose.project` took eight arguments, and the callers worked out the checkout themselves. - `apps._apply` and `status.deploy_record` each turned an exit code into a status and an error text: 0 ok, 3 reverted, any other code failed. - `status.deploy_record` returned a plain dict. The API and `apps._join` read it by string keys. - `test_check_repo_debounces` failed at random on `main`. Since #47, each check runs `podman ps`. That call can take longer than the 50 ms window of the test. ## Changed - `compose.project(config, app, plan)` takes the plan (`Deployed`). It picks the checkout (the clone for a repo app, the app folder otherwise), the image (`compose.plan_image`), the Dockerfile, and the files. `Project` carries `image`. `saved_project` only checks that the files are still there, then calls `project`. - `status.deploy_status(code)` and `status.deploy_error(code, previous)` hold the exit-code rule. `_apply` and `deploy_record` use them. - `status.Record` is the deploy record. `Row.deploy` and `Page.deploy` hold a `Record`. The JSON shapes do not change: `asdict` writes the same keys, and the deploy route still adds `port` on `ok`. - The debounce test uses a 0.5 s window. I did not add a separate `Source` type. When `compose.project` owns the rules, the string checks that remain are each in one place. ## Not done Nothing ## Proof ``` $ just check ... 500 passed in 60.45s ``` `tests/server/test_compose.py` checks the image and the Dockerfile of each kind through the new `project`. `test_check_repo_debounces` passed 5 runs in a row. ## Questions None 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bobowski
added a commit
that referenced
this pull request
Nov 2, 2026
…through Podman.stream (#50) ## Intent Remove the duplicate code of the status report and of the compose runner. Source: the user, in the Yard audit. This pull request stacks on #49. ## Found - `status.report` and `status.app_report` each ran `ps -a`, `stats`, and the crash inspect, and each built the rows of an app. - Each status call sent one Caddy `/adapt` request for each app, through `sites.list_hosts`. `yard status` asked Caddy about every site file every time. - `_row` walked the data folder of an app once for each container of that app. - `compose.py` built its own Podman argv, its own `--remote` flag, and its own copy of `os.environ` (`_podman`, `_process_env`, `_run`). Podman holds the same logic. `_process_env` set `CONTAINER_HOST` only when it was missing. The yard container and the apply image always set it (`ops/compose.yml`, `ops/Containerfile`), so the function changed nothing in production. ## Changed - `status._look` asks Podman once: the containers, the samples, and the crashes. `report` and `app_report` both use it and both use `_app_rows`. `_app_rows` reads the tip, the deploy record, and the data size of the app once. - `_row` takes the data size and no `Config`. - `sites.list_hosts` keeps the hosts of each site file in `Yard.site_hosts`, keyed by the `(mtime_ns, size)` of the file. Caddy adapts a file again only when the file changes. - `Podman.stream(*args, on_line)` runs a command with no timeout and streams each line. `compose` runs `rm`, `run`, `logs -f`, and `build` through it. `_process_env` and `_run` are gone. ## Not done - I did not pass `yard.podman` into `compose`. `compose` makes a `Podman` from the config, so the signatures of `compose.apply` and of the test fakes stay the same. ## Proof ``` $ just check ... 500 passed in 61.46s ``` `test_list_hosts_adapts_each_file_once` shows one `/adapt` for two reads, then a new adapt after the file changes. `test_stream_calls_on_line_in_order` checks the line stream and the exit code. ## Questions - A missing podman binary in a compose call now raises the code `podman` in place of `compose`. Nothing reads that code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bobowski
added a commit
that referenced
this pull request
Nov 2, 2026
…ollow for logs and events (#51) ## 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 #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](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Write each rule of a deploy once: the image of a plan, the Dockerfile path, the compose files, and the status of an exit code. Source: the user, in the Yard audit. This pull request stacks on #48.
Found
apps._ready_repo,apps._ready_source, andcompose.saved_projecteach built the image and the Dockerfile path with their own copy of the rules:split('@')[0]@<digest>two times,compose:<rev[7:19]>two times, and theyard:Dockerfile rule two times.compose.projecttook eight arguments, and the callers worked out the checkout themselves.apps._applyandstatus.deploy_recordeach turned an exit code into a status and an error text: 0 ok, 3 reverted, any other code failed.status.deploy_recordreturned a plain dict. The API andapps._joinread it by string keys.test_check_repo_debouncesfailed at random onmain. Since The deploy container is the only deploy record and the deploy lock #47, each check runspodman ps. That call can take longer than the 50 ms window of the test.Changed
compose.project(config, app, plan)takes the plan (Deployed). It picks the checkout (the clone for a repo app, the app folder otherwise), the image (compose.plan_image), the Dockerfile, and the files.Projectcarriesimage.saved_projectonly checks that the files are still there, then callsproject.status.deploy_status(code)andstatus.deploy_error(code, previous)hold the exit-code rule._applyanddeploy_recorduse them.status.Recordis the deploy record.Row.deployandPage.deployhold aRecord. The JSON shapes do not change:asdictwrites the same keys, and the deploy route still addsportonok.I did not add a separate
Sourcetype. Whencompose.projectowns the rules, the string checks that remain are each in one place.Not done
Nothing
Proof
tests/server/test_compose.pychecks the image and the Dockerfile of each kind through the newproject.test_check_repo_debouncespassed 5 runs in a row.Questions
None
🤖 Generated with Claude Code