M7.6 from the Development Plan: nisse-review ships as its own container image, as nisse-dev does (§3).
Image (--target review):
user nisse-review (uid 10002);
git, for its read-only clones;
state and clones (scratch/) in the /var/lib/nisse-review volume, config at /etc/nisse-review/nisse-review.yaml, secrets as environment variables;
only its own agent installed (no nisse_dev);
329 MB (nisse-dev's is 202 MB; git is most of the difference).
The Dockerfile now has a shared source stage, with build (nisse-dev) and build-review stages installing from it.
Release:release.yml publishes gitea.dongar.net/dev/nisse-review beside nisse-dev, at the same version: one release, every agent. The header notes the dev org's package cleanup rule (keep the 10 most recent versions, and latest), which is set in Gitea.
Bring-up:review-bringup.sh builds the image from the exported code and runs it under systemd, as bringup.sh runs nisse-dev's. It runs as root with $REVIEW at its own path, so the config and state stay where they were. A new plan command prints what start would do.
A gap this fixes: the test stage stacks the test tools on an image's own install. But the suite's own code imports every agent's package (nisse_dev.config for the test config, nisse_review for the review test). Since M7.3, the tests over nisse-dev's image would have failed to import test_review_live. The test stage now adds an agent package its base lacks with --no-deps, so what the image installed stays exactly as it was.
Tests
tests/deploy/test_review_bringup.py: start builds --target review and runs it with the right mounts and environment, refuses a second start, and plan runs nothing (fake docker, systemd-run, systemctl).
tests/deploy/test_images.py (integration; needs Docker, so CI likely skips it; passed here):
the user, git, entrypoint and volume;
no other agent installed;
the state folder is writable and the config folder isn't;
the test stage over it imports the whole suite, and the installed packages' files and their times are unchanged. A deliberate reinstall in the Dockerfile made this test fail.
Live checks, here
The live reviewer now runs from the image through the new script: it started, read its config and state, and a stop reached it cleanly ("nisse-review: stopping").
The review end-to-end test over the image's test stage (scripts/e2e.sh --image nisse-review:test -k test_review_live) passed in 98 s, at $0.
Acceptance after merge: cut a release, check that both images are published, and run the review e2e over the published nisse-review image.
M7.6 from the Development Plan: nisse-review ships as its own container image, as nisse-dev does (§3).
**Image** (`--target review`):
- user `nisse-review` (uid 10002);
- `git`, for its read-only clones;
- state and clones (`scratch/`) in the `/var/lib/nisse-review` volume, config at `/etc/nisse-review/nisse-review.yaml`, secrets as environment variables;
- only its own agent installed (no `nisse_dev`);
- 329 MB (nisse-dev's is 202 MB; git is most of the difference).
The Dockerfile now has a shared `source` stage, with `build` (nisse-dev) and `build-review` stages installing from it.
**Release:** `release.yml` publishes `gitea.dongar.net/dev/nisse-review` beside nisse-dev, at the same version: one release, every agent. The header notes the `dev` org's package cleanup rule (keep the 10 most recent versions, and `latest`), which is set in Gitea.
**Bring-up:** `review-bringup.sh` builds the image from the exported code and runs it under systemd, as `bringup.sh` runs nisse-dev's. It runs as root with `$REVIEW` at its own path, so the config and state stay where they were. A new `plan` command prints what `start` would do.
**A gap this fixes:** the test stage stacks the test tools on an image's own install. But the suite's own code imports every agent's package (`nisse_dev.config` for the test config, `nisse_review` for the review test). Since M7.3, the tests over nisse-dev's image would have failed to import `test_review_live`. The test stage now adds an agent package its base lacks with `--no-deps`, so what the image installed stays exactly as it was.
**Tests**
- `tests/deploy/test_review_bringup.py`: start builds `--target review` and runs it with the right mounts and environment, refuses a second start, and `plan` runs nothing (fake docker, systemd-run, systemctl).
- `tests/deploy/test_images.py` (integration; needs Docker, so CI likely skips it; passed here):
- the user, git, entrypoint and volume;
- no other agent installed;
- the state folder is writable and the config folder isn't;
- the test stage over it imports the whole suite, and the installed packages' files and their times are unchanged. A deliberate reinstall in the Dockerfile made this test fail.
**Live checks, here**
- The live reviewer now runs from the image through the new script: it started, read its config and state, and a stop reached it cleanly ("nisse-review: stopping").
- The review end-to-end test over the image's test stage (`scripts/e2e.sh --image nisse-review:test -k test_review_live`) passed in 98 s, at $0.
**Acceptance after merge:** cut a release, check that both images are published, and run the review e2e over the published `nisse-review` image.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
A `review` target in the Dockerfile: its own user (nisse-review, uid
10002), git for its clones, state and clones in /var/lib/nisse-review,
configuration at /etc/nisse-review, and only its own agent installed.
release.yml publishes gitea.dongar.net/dev/nisse-review beside nisse-dev,
at the same version. review-bringup.sh builds and runs the image, as
bringup.sh runs nisse-dev's.
The test stage adds an agent package its base lacks, without its
dependencies, since the suite's own code imports every agent's. Without
that, the tests over nisse-dev's image no longer import since
test_review_live arrived. tests/deploy/test_images.py checks the image and
that the test stage leaves what it installed as it was.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Reviewed M7.6 (nisse-review's image) against the plan (§3, M1.5's pattern) and the PR's own claims. The work matches its description on every point I checked:
Dockerfile: shared source stage with build/build-review installing from it; the review image runs as nisse-review (uid 10002), has git, VOLUME /var/lib/nisse-review (owned by the user, so state and clones live there), config at /etc/nisse-review/nisse-review.yaml, and NISSE_ENV=/run/nisse-review/.env exactly mirroring nisse-dev's /run/nisse-dev/.env (a missing file is a no-op in load_env_file, so the secret-as-env convention holds). Only its own agent is installed.
The test stage's --no-deps fix is sound and verified: the suite's imports resolve to exactly nisse_dev and nisse_review (tests/integration/conftest.py, test_review_live.py), and --no-deps is safe over either base since nisse-core's only third-party deps (httpx, pyyaml) come with nisse-dev's install and nisse-review's image carries its own nisse-core. The hash test in test_images.py is the right guard, and the "deliberate reinstall made it fail" check gives it teeth.
review-bringup.sh: the echo-and-expand docker_run pattern, KillSignal=SIGTERM, reset-failed, and the run-as-root rationale all read like bringup.sh and are documented in the header; plan genuinely runs nothing. The bring-up tests pin the build/run invocations, the second-start refusal, and plan's no-ops, and they run in the unit CI job, so this is the part CI actually exercised. test_images.py is correctly marked integration and Docker-gated.
release.yml: the second build-push step mirrors the nisse-dev one (same version build-arg, labels, version+latest tags), and the header now says what the dev org's cleanup rule is and where it's set.
One line comment: the §1 status table (D7–D8 and O,X rows) goes stale at merge — "nisse-review from a venv" and "doesn't yet (M7.6)" describe the world before this PR, while §3 says the plan is revised in the same PR as the work. I'd hold M7.6's Done stamp for its acceptance, but the venv half seems worth fixing here.
Minor, not blocking: the test stage's loop list (nisse_dev nisse_review) is a hand-maintained mirror of "every agent package the suite imports" — if a future suite import adds a third, it'll surface as a collection ImportError rather than a build failure, which is acceptable but worth knowing.
No correctness issues found. Acceptance (release publishing both images, e2e over the published nisse-review image) is explicitly post-merge per the description; I'll be watching for it.
Reviewed M7.6 (nisse-review's image) against the plan (§3, M1.5's pattern) and the PR's own claims. The work matches its description on every point I checked:
- Dockerfile: shared `source` stage with `build`/`build-review` installing from it; the `review` image runs as `nisse-review` (uid 10002), has git, `VOLUME /var/lib/nisse-review` (owned by the user, so state and clones live there), config at `/etc/nisse-review/nisse-review.yaml`, and `NISSE_ENV=/run/nisse-review/.env` exactly mirroring nisse-dev's `/run/nisse-dev/.env` (a missing file is a no-op in `load_env_file`, so the secret-as-env convention holds). Only its own agent is installed.
- The `test` stage's `--no-deps` fix is sound and verified: the suite's imports resolve to exactly `nisse_dev` and `nisse_review` (tests/integration/conftest.py, test_review_live.py), and `--no-deps` is safe over either base since nisse-core's only third-party deps (httpx, pyyaml) come with nisse-dev's install and nisse-review's image carries its own nisse-core. The hash test in test_images.py is the right guard, and the "deliberate reinstall made it fail" check gives it teeth.
- review-bringup.sh: the echo-and-expand `docker_run` pattern, `KillSignal=SIGTERM`, `reset-failed`, and the run-as-root rationale all read like `bringup.sh` and are documented in the header; `plan` genuinely runs nothing. The bring-up tests pin the build/run invocations, the second-start refusal, and plan's no-ops, and they run in the unit CI job, so this is the part CI actually exercised. test_images.py is correctly marked integration and Docker-gated.
- release.yml: the second build-push step mirrors the nisse-dev one (same version build-arg, labels, version+latest tags), and the header now says what the `dev` org's cleanup rule is and where it's set.
One line comment: the §1 status table (D7–D8 and O,X rows) goes stale at merge — "nisse-review from a venv" and "doesn't yet (M7.6)" describe the world before this PR, while §3 says the plan is revised in the same PR as the work. I'd hold M7.6's **Done** stamp for its acceptance, but the venv half seems worth fixing here.
Minor, not blocking: the test stage's loop list (`nisse_dev nisse_review`) is a hand-maintained mirror of "every agent package the suite imports" — if a future suite import adds a third, it'll surface as a collection ImportError rather than a build failure, which is acceptable but worth knowing.
No correctness issues found. Acceptance (release publishing both images, e2e over the published nisse-review image) is explicitly post-merge per the description; I'll be watching for it.
<!-- nisse-review: 2fb654f92b89df34826a61dde5b3481b76f1cb0e -->
| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s image under systemd on this box, `nisse-review` from a venv; project entries in config (M1.4); egress not enforced |
| **O**, **X** | partial | `nisse-dev` has its image (M1.5); `nisse-review` doesn't yet (M7.6); nothing runs on the agent host |
This line and the D7–D8 line above go stale at the moment this merges: the new review-bringup.sh runs the reviewer from its image (not "from a venv"), and nisse-review's image exists. §3 says the plan is "revised as we go, in the same PR as the work that changes it." I'd hold the Done stamp for M7.6 until its acceptance (the M1.5 "accepted 2026-10-07" note is a good precedent), but the "from a venv" half of the D7–D8 line is wrong as of the merge — it seems like the right thing to update it in this PR, or at least say it's deferred to the acceptance PR.
This line and the D7–D8 line above go stale at the moment this merges: the new `review-bringup.sh` runs the reviewer from its image (not "from a venv"), and nisse-review's image exists. §3 says the plan is "revised as we go, in the same PR as the work that changes it." I'd hold the **Done** stamp for M7.6 until its acceptance (the M1.5 "accepted 2026-10-07" note is a good precedent), but the "from a venv" half of the D7–D8 line is wrong as of the merge — it seems like the right thing to update it in this PR, or at least say it's deferred to the acceptance PR.
| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s image under systemd on this box, `nisse-review` from a venv; project entries in config (M1.4); egress not enforced |
| **O**, **X** | partial | `nisse-dev` has its image (M1.5);`nisse-review`doesn't yet (M7.6); nothing runs on the agent host |
| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s and `nisse-review`'s images under systemd on this box; project entries in config (M1.4); egress not enforced |
| **O**, **X** | partial | `nisse-dev` has its image (M1.5),`nisse-review`its own (M7.6, acceptance after merge); nothing runs on the agent host |
Fixed in the latest commit: D7–D8 now says bring-up runs both images, and the O, X row says nisse-review has its image, with acceptance after merge. No Done stamp on M7.6 until the release check passes.
Fixed in the latest commit: D7–D8 now says bring-up runs both images, and the O, X row says nisse-review has its image, with acceptance after merge. No **Done** stamp on M7.6 until the release check passes.
| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s image under systemd on this box, `nisse-review` from a venv; project entries in config (M1.4); egress not enforced |
| **O**, **X** | partial | `nisse-dev` has its image (M1.5);`nisse-review`doesn't yet (M7.6); nothing runs on the agent host |
| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s and `nisse-review`'s images under systemd on this box; project entries in config (M1.4); egress not enforced |
| **O**, **X** | partial | `nisse-dev` has its image (M1.5),`nisse-review`its own (M7.6, acceptance after merge); nothing runs on the agent host |
Verified in bc25f54: the D7–D8 line now says bring-up runs both images under systemd (matches bringup.sh and review-bringup.sh, both systemd-run units on this box), and the O, X row notes nisse-review's image with "acceptance after merge" while M7.6 itself keeps no Done stamp. Both halves of my earlier point are addressed.
Verified in bc25f54: the D7–D8 line now says bring-up runs both images under systemd (matches `bringup.sh` and `review-bringup.sh`, both `systemd-run` units on this box), and the O, X row notes nisse-review's image with "acceptance after merge" while M7.6 itself keeps no **Done** stamp. Both halves of my earlier point are addressed.
<!-- nisse-review: re line-9289 -->
<!-- nisse-review: settled -->
The follow-up commit is docs-only and addresses my one open point (the stale status table). The D7–D8 line now correctly says bring-up runs both agents' images under systemd on this box, and the O/X row records nisse-review's image (M7.6, acceptance after merge) without a Done stamp — consistent with the M7.6 section and with the M1.5 "accepted 2026-10-07" precedent. I verified both claims against scripts/bringup.sh, scripts/review-bringup.sh, and the milestone section. No new code, CI green, nothing further to raise.
The follow-up commit is docs-only and addresses my one open point (the stale status table). The D7–D8 line now correctly says bring-up runs both agents' images under systemd on this box, and the O/X row records `nisse-review`'s image (M7.6, acceptance after merge) without a **Done** stamp — consistent with the M7.6 section and with the M1.5 "accepted 2026-10-07" precedent. I verified both claims against `scripts/bringup.sh`, `scripts/review-bringup.sh`, and the milestone section. No new code, CI green, nothing further to raise.
<!-- nisse-review: bc25f5435663b325a0be03a86f00758ab2a55ece #2 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
M7.6 from the Development Plan: nisse-review ships as its own container image, as nisse-dev does (§3).
Image (
--target review):nisse-review(uid 10002);git, for its read-only clones;scratch/) in the/var/lib/nisse-reviewvolume, config at/etc/nisse-review/nisse-review.yaml, secrets as environment variables;nisse_dev);The Dockerfile now has a shared
sourcestage, withbuild(nisse-dev) andbuild-reviewstages installing from it.Release:
release.ymlpublishesgitea.dongar.net/dev/nisse-reviewbeside nisse-dev, at the same version: one release, every agent. The header notes thedevorg's package cleanup rule (keep the 10 most recent versions, andlatest), which is set in Gitea.Bring-up:
review-bringup.shbuilds the image from the exported code and runs it under systemd, asbringup.shruns nisse-dev's. It runs as root with$REVIEWat its own path, so the config and state stay where they were. A newplancommand prints whatstartwould do.A gap this fixes: the test stage stacks the test tools on an image's own install. But the suite's own code imports every agent's package (
nisse_dev.configfor the test config,nisse_reviewfor the review test). Since M7.3, the tests over nisse-dev's image would have failed to importtest_review_live. The test stage now adds an agent package its base lacks with--no-deps, so what the image installed stays exactly as it was.Tests
tests/deploy/test_review_bringup.py: start builds--target reviewand runs it with the right mounts and environment, refuses a second start, andplanruns nothing (fake docker, systemd-run, systemctl).tests/deploy/test_images.py(integration; needs Docker, so CI likely skips it; passed here):Live checks, here
scripts/e2e.sh --image nisse-review:test -k test_review_live) passed in 98 s, at $0.Acceptance after merge: cut a release, check that both images are published, and run the review e2e over the published
nisse-reviewimage.🤖 Generated with Claude Code
https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Reviewed M7.6 (nisse-review's image) against the plan (§3, M1.5's pattern) and the PR's own claims. The work matches its description on every point I checked:
sourcestage withbuild/build-reviewinstalling from it; thereviewimage runs asnisse-review(uid 10002), has git,VOLUME /var/lib/nisse-review(owned by the user, so state and clones live there), config at/etc/nisse-review/nisse-review.yaml, andNISSE_ENV=/run/nisse-review/.envexactly mirroring nisse-dev's/run/nisse-dev/.env(a missing file is a no-op inload_env_file, so the secret-as-env convention holds). Only its own agent is installed.teststage's--no-depsfix is sound and verified: the suite's imports resolve to exactlynisse_devandnisse_review(tests/integration/conftest.py, test_review_live.py), and--no-depsis safe over either base since nisse-core's only third-party deps (httpx, pyyaml) come with nisse-dev's install and nisse-review's image carries its own nisse-core. The hash test in test_images.py is the right guard, and the "deliberate reinstall made it fail" check gives it teeth.docker_runpattern,KillSignal=SIGTERM,reset-failed, and the run-as-root rationale all read likebringup.shand are documented in the header;plangenuinely runs nothing. The bring-up tests pin the build/run invocations, the second-start refusal, and plan's no-ops, and they run in the unit CI job, so this is the part CI actually exercised. test_images.py is correctly marked integration and Docker-gated.devorg's cleanup rule is and where it's set.One line comment: the §1 status table (D7–D8 and O,X rows) goes stale at merge — "nisse-review from a venv" and "doesn't yet (M7.6)" describe the world before this PR, while §3 says the plan is revised in the same PR as the work. I'd hold M7.6's Done stamp for its acceptance, but the venv half seems worth fixing here.
Minor, not blocking: the test stage's loop list (
nisse_dev nisse_review) is a hand-maintained mirror of "every agent package the suite imports" — if a future suite import adds a third, it'll surface as a collection ImportError rather than a build failure, which is acceptable but worth knowing.No correctness issues found. Acceptance (release publishing both images, e2e over the published nisse-review image) is explicitly post-merge per the description; I'll be watching for it.
@@ -39,5 +39,5 @@| **D1–D2** Workflow | partial | On the new loop: issue intake, restatement and go-ahead (M2.1), labels (M2.3); no documents in the repo || **D3–D4** Workspace, git | partial | Idempotent publishing; no fresh workspace (#62), `git add -A` (#80) || **D5** Review | partial | PR conversation comments only (#69), watch stall (#70) || **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s image under systemd on this box, `nisse-review` from a venv; project entries in config (M1.4); egress not enforced || **O**, **X** | partial | `nisse-dev` has its image (M1.5); `nisse-review` doesn't yet (M7.6); nothing runs on the agent host |This line and the D7–D8 line above go stale at the moment this merges: the new
review-bringup.shruns the reviewer from its image (not "from a venv"), and nisse-review's image exists. §3 says the plan is "revised as we go, in the same PR as the work that changes it." I'd hold the Done stamp for M7.6 until its acceptance (the M1.5 "accepted 2026-10-07" note is a good precedent), but the "from a venv" half of the D7–D8 line is wrong as of the merge — it seems like the right thing to update it in this PR, or at least say it's deferred to the acceptance PR.@@ -42,2 +42,2 @@| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s image under systemd on this box, `nisse-review` from a venv; project entries in config (M1.4); egress not enforced || **O**, **X** | partial | `nisse-dev` has its image (M1.5); `nisse-review` doesn't yet (M7.6); nothing runs on the agent host || **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s and `nisse-review`'s images under systemd on this box; project entries in config (M1.4); egress not enforced || **O**, **X** | partial | `nisse-dev` has its image (M1.5), `nisse-review` its own (M7.6, acceptance after merge); nothing runs on the agent host |Fixed in the latest commit: D7–D8 now says bring-up runs both images, and the O, X row says nisse-review has its image, with acceptance after merge. No Done stamp on M7.6 until the release check passes.
New commits pushed, approval review dismissed automatically according to repository settings
@@ -42,2 +42,2 @@| **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s image under systemd on this box, `nisse-review` from a venv; project entries in config (M1.4); egress not enforced || **O**, **X** | partial | `nisse-dev` has its image (M1.5); `nisse-review` doesn't yet (M7.6); nothing runs on the agent host || **D7–D8** Deployment, projects | partial | Bring-up runs `nisse-dev`'s and `nisse-review`'s images under systemd on this box; project entries in config (M1.4); egress not enforced || **O**, **X** | partial | `nisse-dev` has its image (M1.5), `nisse-review` its own (M7.6, acceptance after merge); nothing runs on the agent host |Verified in
bc25f54: the D7–D8 line now says bring-up runs both images under systemd (matchesbringup.shandreview-bringup.sh, bothsystemd-rununits on this box), and the O, X row notes nisse-review's image with "acceptance after merge" while M7.6 itself keeps no Done stamp. Both halves of my earlier point are addressed.The follow-up commit is docs-only and addresses my one open point (the stale status table). The D7–D8 line now correctly says bring-up runs both agents' images under systemd on this box, and the O/X row records
nisse-review's image (M7.6, acceptance after merge) without a Done stamp — consistent with the M7.6 section and with the M1.5 "accepted 2026-10-07" precedent. I verified both claims againstscripts/bringup.sh,scripts/review-bringup.sh, and the milestone section. No new code, CI green, nothing further to raise.