Repository navigation
Conversation
Switch _src_path from copier-python-package-template to copier-nuxt-python-intranet-app (v0.0.188). Copier applied cleanly with no .rej files. This commit is the raw template output only. The existing src/, tests/, pyproject.toml, pyinstaller.spec, docs/, .readthedocs.yaml and .github/.codecov.yml are untouched at the repo root and are reconciled in the commits that follow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Relocate the package and tests into the layout the nuxt template expects, renaming cloud_courier -> backend_api. Collisions resolved: - logger_config.py: dropped ours in favour of the template's (a strict superset). SUBSYSTEM_NAME goes "courier" -> "backend", so log files are now cloud-courier-backend.log and the JSON "subsystem" field changes. - cli.py: its seven arguments folded into the template's entrypoint/parser.py; the file is deleted. get_version() has a different shape there (bare version, prepend_v flag), so main.py's instance-tag write uses get_version(prepend_v=True) to keep the tag value identical for the cloud-courier-infrastructure Pulumi code. - conftest.py: localstack_profile, flag_file_dir, --only-exe and its collection hook merged into backend/tests/conftest.py; the only_exe marker is restored to pytest.ini. Making --aws-region and --stop-flag-dir required broke five of the template's own arg-parsing tests and its crash-dump resolution, which silently falls back to the temp dir when the argv it is handed does not parse. Both are fixed by filling in GENERIC_REQUIRED_CLI_ARGS, the seam the template leaves for exactly this. backend/pyproject.toml takes version 0.0.5, continuing the version line from the deleted root pyproject.toml, since release.yaml now reads the version from there. The OpenAPI snapshot is regenerated for that change. Also restores the localstack service to .devcontainer/docker-compose.yml, which the template dropped. This needs a devcontainer rebuild to take effect -- and so does the python_venv volume, which the template moved to backend/.venv. Until that rebuild, backend/.venv sits on the 9p Windows mount, where botocore's per-client scan of its ~400 service data directories is slow enough to blow past the thread timeouts in test_main.py. The suite is green (113 passed, 1 skipped, 20s) against a venv on a normal filesystem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The exe runs backend_api.entrypoint.cli.entrypoint, which never reached
main.entrypoint, so the upload agent was dead code after the tree move.
Fold it into the server's lifecycle instead.
main.entrypoint becomes start_courier(cli_args, *, stop_event). start_app
stashes the CLI arguments and the stop event on app.state, and the app's
lifespan reads them to run the agent on its own thread, joining it on
teardown. This follows the app.state.port convention in the sibling
weight-sensor-driver repo, and ties the agent to uvicorn's lifecycle so
there is one shutdown chain rather than two:
SvcStop -> stop_event -> server.should_exit -> uvicorn shutdown
-> lifespan teardown -> agent stops, thread joins
MainLoop now takes that stop event and waits on it instead of sleeping
between iterations, so a shutdown is noticed immediately rather than
after the remainder of the idle interval. The join is bounded at 20s,
inside the 30s grace period SvcStop allows. Whatever stops the agent
stops the server with it, and a crashed agent exits non-zero so the
service control manager does not read it as a clean admin stop.
--aws-region and --stop-flag-dir are no longer required by argparse.
Requiring them would have forced every caller that only wants the server
to invent placeholder values, and it does not help the E2E runners, which
have no AWS account to reach. They are validated in start_courier
instead, where a misconfigured deployment still fails immediately and
says which argument is missing. The new --skip-upload-agent lets the E2E
and Windows service harnesses serve the API alone. This also restores
tests/unit/entrypoint/test_arg_parsing.py and test_crash_dump.py to their
template versions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two deliberate divergences from the upstream template, both in the unit-test-backend job, both commented in place so a future copier update does not quietly revert them and a reviewer does not try to backport them. use_windows_in_ci has to be true for the MSI installer to be built, which also puts a Windows runner on this matrix. Nearly every backend unit test reaches AWS through a localstack service container, and GitHub runs Linux service containers only on Linux runners, so the Windows entry is removed here while build-backend and build-installer keep theirs. The template ships no service containers at all, so localstack and the AWS profile step are restored from the pre-migration workflow. The profile script already resolves the endpoint to localhost under GITHUB_ACTIONS, which is what the published port maps to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copier left frontend/package.json and pnpm-workspace.yaml without a pnpm-lock.yaml, so the Pre-commit job failed with ERR_PNPM_NO_LOCKFILE. That job gates the whole workflow, so every other job was skipped and nothing on this branch had been exercised by CI at all. Generated from the repo root with pnpm 12.4.2, so the workspace overrides and allowBuilds settings apply: 951 packages. The devcontainer context hash is regenerated by its pre-commit hook, which tracks lockfiles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two CI failures from the first run that got past the pre-commit gate. build-installer died on WIX0103: Product.wxs requires $(BrandingDir)\favicon.ico, and installer_branding_dir defaults to frontend/public, which the template never creates. Add that favicon, copied from the backend's static one. A Nuxt app wants a public favicon regardless. The Playwright suite launches the executable itself, in frontend/tests/e2e/global-setup.ts, separately from the Python app_bootup.start_exe helper. It was still starting the agent, which cannot reach AWS from a CI runner, so the agent raised MissingCourierArgumentError, set the shared stop event and took the server down with it before it ever bound its port. Pass --skip-upload-agent there too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
frontend/tests/e2e/fixtures.ts imports the Kiota-generated backend client, but instantiation never produced it, so Playwright could not resolve the import and reported "Cannot find package '~'" followed by "No tests found". Generated with `task gen-frontend-openapi-client`. The output lands under frontend/app/generated/ rather than frontend/generated/, because Nuxt aliases ~ to the app directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nuxt template ships no docs scaffold, so ci.yaml lost the job that built them, and .readthedocs.yaml still installed the project with `path: .` even though the root pyproject.toml is gone, which would have broken the ReadTheDocs build. Nothing in the docs imports the package: every page is hand-written reStructuredText with no autodoc directives, and conf.py loads sphinx.ext.autodoc without any page using it. A -W build with only docs/requirements.txt installed succeeds, so the install entry is dropped rather than repointed at backend/. The new build-docs job installs the same way, via `uv run --no-project --with-requirements requirements.txt`, so CI and ReadTheDocs cannot drift apart. Sphinx stays out of backend/pyproject.toml since the backend does not need it. Also drops the copier-python-package-template entry from .config/.copier-managed-files.json. This repo no longer instantiates from that template, and three of the files the entry listed are deleted. The warning banners in the docs files said to backport changes there, so they now say the files are maintained here instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nuxt template does not use Codecov, so the upload step went with the workflow it replaced, leaving .github/.codecov.yml orphaned and the badge missing from the README. Two differences from the step this replaces: the report is at backend/coverage.xml, because uv --directory runs pytest with backend/ as its working directory, and the matrix.JOB_MATCHING_DEV_ENV guard is gone since that variable no longer exists now the matrix is a single Linux entry. The dependabot guard stays. Also restores the ReadTheDocs badge, dropped by the same template switch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- initialize-command.sh writes .devcontainer/.env so the compose bind mount can be retargeted when VS Code clones into a container volume, and docker-compose.yml reads it via WORKSPACE_MOUNT_TARGET - build-installer gains the Windows long-file-paths step - the XML formatter no longer rewrites .wxs files, which it was making unreadable by sorting and joining attributes - install-ci-tooling.py refactor and extension version bumps One hunk was rejected, in the unit-test-backend job, because the template inserted its long-file-paths step exactly where this repo removes the Windows runner and adds the localstack service container. Re-applied by hand. That step is dropped along with the Windows entry: it is a no-op on Linux, and keeping it would imply the matrix still has a Windows runner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
installer_manufacturer was answered "Lab Automation and SCreening" at instantiation. Re-answered and regenerated. Beyond the displayed name, this also corrects the install directory under Program Files, the ProgramData log directory and the HKLM registry key, all of which derive from that answer. Nothing has been released from this branch, so no installed machine carries the misspelled paths. installer_upgrade_code and WINDOWS_SERVICE_NAME are deliberately unchanged: the upgrade code identifies MSI upgrades for the life of the product, and the service name is the identity the service control manager keys on. Only the display name moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A too-new file is re-queued rather than dropped, so nothing is lost, but it is only reconsidered on the next loop iteration, and every iteration makes an STS call. How long the file waits therefore tracks network latency rather than delay_seconds_before_upload. Separately, _boot_up stamps its events with the moment the file was discovered instead of the file's mtime, so a long-settled file waits out the full delay again after every restart. Both surfaced as an intermittent failure in TestInitialFolderSearch::test_Given_folder_config_includes_subfolders..., which allows one second for the upload to land. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tests/executable/test_intact.py ran the built executable with --version and asserted the output started with "v". It never ran anywhere: it was marked only_exe, the collection hook skipped that marker unless --only-exe was passed, and no job ever passed it. The file still carried a TODO to wire it into CI. The template's E2E suite now launches the same executable for real and drives it over HTTP, so the version assertion moves there, into a repo-specific spec that runs on both Linux and Windows. It covers slightly more than the old test did: the version is resolved at runtime from installed package metadata, so this fails if a PyInstaller build stops bundling that metadata, which is only observable once frozen. With nothing left carrying the marker, the --only-exe option, the collection hook and the only_exe marker registration all go too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every test in this file installs the service with --skip-upload-agent, so it covers the service lifecycle rather than the agent running under it. That gap is deliberate for now: the MSI arrives with the nuxt template, the repo shipped no installer before it, and wiring it up for lab use is out of scope for the migration. The four open questions are recorded where that gap is masked. The first two would each produce a service that fails on a real machine: nothing supplies --aws-region or --stop-flag-dir, and the AWS credentials are read from LocalSystem's profile, so the installer's custom-account option cannot authenticate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The migration surfaced several things worth fixing that are out of scope for it. Each is recorded where someone would actually hit it rather than in a tracker nobody reads: - the /api/shutdown route calls os._exit, which skips the multipart abort handler and so can orphan parts in the bucket, and is unauthenticated on a service whose bind address is operator-selectable - the upload agent treats AWS being unreachable at startup as fatal and takes the server down with it, which is also why the E2E and Windows service harnesses have to pass --skip-upload-agent - the stop-flag-file mechanism is superseded by the service stop event, but cannot be removed until whatever writes those files is found - the unit tests only ever run on Linux for a Windows-only product, which needs the AWS-touching tests moved off localstack first - the MSI installer is not yet documented because it is not yet usable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 166 files, which is 66 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Repository: LabAutomationAndScreening/cloud-courier/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: ⛔ Files ignored due to path filters (19)
📒 Files selected for processing (166)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests.
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
get_version returns a bare version by default, so dropping prepend_v would silently change what cloud-courier-infrastructure reads off the instance tag. The stubber compares against the mocked return value and cannot tell whether prepend_v was passed, so assert the call itself. Verified by dropping prepend_v and confirming the test fails. The comment that used to sit on that line is removed rather than reworded: the tag key already carries a warning about the Pulumi code that reads it, and the behaviour is now enforced instead of described. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Link to Issue or Message thread
Why is this change necessary?
cloud-courier was instantiated from
copier-python-package-template, which has no notion of installing theagent as a Windows service. Nothing in the repo or the docs covered service installation at all —
docs/configure-lab-computers.rstonly covered upload configuration.copier-nuxt-python-intranet-appsupplies a signed-capable MSI, service registration, an LSA rights grant,crash dumps, and a Windows CI job that installs and exercises the service. It is also actively maintained
and sits on a much newer
copier-base-templatepin.How does this change address the issue?
Switches
_src_pathto the nuxt template and reconciles the repo with the layout it expects.src/cloud_courier/→backend/src/backend_api/, tests alongside it, rootpyproject.toml/uv.lock/pyinstaller.specremoved in favour of thebackend/ones.backend/pyproject.tomlcarries version0.0.5to continue the existing version line, which is whatrelease.yamltags from.SCM stop drains the agent instead of killing it. This is the only non-mechanical change in the PR; the
rest is relocation or restoration.
Scope is deliberately minimal. The MSI and Windows service arrive with the template but are not wired up
for real use: nothing yet supplies the agent with
--aws-regionor--stop-flag-dir, so a real installwould register a service that fails on first start. That is fine for now because the repo shipped no
installer before this, so nothing regresses, and the manual deployment described in
docs/installation.rstis still accurate. The open questions are recorded as TODOs above
_install_serviceinbackend/tests/windows_service/test_service_lifecycle.py.What side effects does this change have?
Deliberate divergences from the upstream template, each commented in place so they are not mistakenly
backported:
unit-test-backendmatrix.use_windows_in_cihas to be true to build theMSI, but nearly every backend unit test reaches AWS through a localstack service container, and GitHub
only runs Linux service containers on Linux runners.
to the devcontainer compose file. A devcontainer rebuild is needed to pick that up, and also to move
the
python_venvvolume tobackend/.venv; until then the venv sits on the slow host mount.build-docsand the Codecov upload are re-added to CI; the template has neither.--skip-upload-agentflag lets the E2E and Windows service harnesses serve the API without theagent, since CI runners have no AWS to reach.
Behaviour changes worth knowing about:
cloud-courier-backend.lograther thancloud-courier-courier.log, and the JSONsubsystemfield changes, because the template'slogger_config.pyreplaced ours.--aws-regionand--stop-flag-dirare no longerrequired=Trueat the argparse layer. They arevalidated in
start_courier, which raises naming the missing flag, so a misconfigured deployment stillfails immediately and says why.
ProgramData log directory and an HKLM registry key. Nothing has been released from this branch, so no
machine carries the old paths.
installer_upgrade_codeandWINDOWS_SERVICE_NAMEare deliberatelyunchanged.
How is this change tested?
CI is green on every job: 120 backend unit tests at 100% coverage, frontend unit tests, both exe builds, the
MSI build, both E2E suites and the Windows service smoke tests.
Two things gained real coverage they did not have before:
backend/tests/unit/test_courier_lifespan.py: the agent isskipped when unconfigured, started with the right arguments, the stop event propagates on shutdown, a
crash is recorded and produces a non-zero exit, and a hung agent logs the join timeout.
both Linux and Windows. The test it replaces,
tests/executable/test_intact.py, never ran anywhere: itwas marked
only_exe, and no job ever passed--only-exe.Other
Three artifacts a fresh instantiation needs but does not produce had to be generated, each found by a
separate CI run:
pnpm-lock.yaml,frontend/public/favicon.ico(the installer icon), and the frontendKiota client under
frontend/app/generated/. The favicon one is a template bug, filed upstream ascopier-nuxt-python-intranet-app#235.
Follow-up work found along the way is recorded as TODOs in the code rather than in a tracker: the
/api/shutdownroute callsos._exitand can orphan S3 multipart parts; the agent treats AWS beingunreachable at startup as fatal; the stop-flag-file mechanism is superseded by the service stop event but
cannot be removed until whatever writes those files is found; the unit tests only ever run on Linux for a
Windows-only product; and an upload-delay retry whose cadence is bound to network latency.
🤖 Generated with Claude Code