Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ early_access: true
reviews:
profile: assertive
path_instructions:
- path: "**/generated/open-api/**"
instructions: "This is generated client code for the API. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it."
- path: "**/generated/open_api/**"
instructions: "This is generated client code for the API. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it."
- path: "**/generated/graphql.ts"
instructions: "This is generated client code for the API. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it."
- path: "**/vendor_files/**"
instructions: "These files came from a vendor and we're not allowed to change them. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it."
- path: "**/*.py"
Expand Down
38 changes: 26 additions & 12 deletions .config/.copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
# Changes here will be overwritten by Copier
_commit: v0.0.76
_src_path: gh:LabAutomationAndScreening/copier-python-package-template
_commit: v0.0.191
_src_path: gh:LabAutomationAndScreening/copier-nuxt-python-intranet-app.git
auth_to_ecr_for_e2e_in_ci: false
backend_deployed_port_number: 4000
backend_makes_api_calls: false
backend_rest_api_description: Agent to upload files to cloud
backend_uses_graphql: false
configure_python_asyncio: false
configure_vcrpy: false
create_docs: true
deploy_as_executable: true
description: Agent to upload files to cloud
full_repo_url: https://github.com/LabAutomationAndScreening/cloud-courier
extra_windows_service_rights: []
frontend_uses_graphql: false
frontend_uses_zod: false
has_backend: true
human_friendly_app_name: Cloud Courier
install_as_windows_service: true
install_aws_ssm_port_forwarding_plugin: false
install_claude_cli: true
installer_branding_dir: frontend/public
installer_manufacturer: Lab Automation and Screening
installer_upgrade_code: 65bd762e-bd98-4ead-ae8f-2eeee6f1d754
is_circuit_python_driver: false
is_open_source: true
package_name: cloud-courier
primary_author: Eli Fine
node_version: 24.11.1
pnpm_minimum_release_age_exclude: ''
pull_from_ecr: false
python_ci_versions:
- 3.13.9
python_package_registry: PyPI
python_version: 3.13.9
repo_name: cloud-courier
repo_names_for_codespaces_read_permissions: ''
repo_org_name: LabAutomationAndScreening
ssh_port_number: 61335
use_codecov: true
use_windows_in_ci: false
use_windows_in_exe_ci: true
run_backend_e2e_in_ci: false
sign_installer: false
ssh_port_number: 51779
use_upx_for_executable: false
use_windows_in_ci: true
windows_service_bind_policy: configurable_loopback_default
windows_service_org_prefix: LabAutomationAndScreening-

136 changes: 118 additions & 18 deletions .config/.copier-managed-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
".claude/skills/fix-mutants/utils.py",
".claude/skills/fix-mutants/verify-mutant.py",
".coderabbit.yaml",
".config/.coveragerc",
".config/.prettierrc",
".config/_typos.toml",
".config/pylintrc.toml",
Expand Down Expand Up @@ -103,34 +102,135 @@
"README.md",
"Taskfile.yaml",
"biome.jsonc",
"pyproject.toml",
"pnpm-workspace.yaml",
"pytest.ini",
"scripts/delete_false_positive_rej.py",
"sh.bat",
"tests/__init__.py",
"tests/conftest.py"
"sh.bat"
]
},
{
"src": "gh:LabAutomationAndScreening/copier-python-package-template",
"src": "gh:LabAutomationAndScreening/copier-nuxt-python-intranet-app.git",
"parent_src": "gh:LabAutomationAndScreening/copier-base-template.git",
"managed_files": [
".config/.copier-answers.yml",
".config/eslint-rules/istanbul-ignore-if-must-throw.mjs",
".config/taskfiles/backend.yaml",
".config/taskfiles/codegen.yaml",
".config/taskfiles/executable-stack.yaml",
".config/taskfiles/frontend.yaml",
".config/vacuum-functions/bodyDescription.js",
".config/vacuum-functions/bodyExample.js",
".config/vacuum-functions/resourcesPlural.js",
".config/vacuum-ignore.yaml",
".config/vacuum-ruleset.yaml",
".devcontainer/code-artifact-auth.sh",
".github/.codecov.yml",
".github/actions/ecr-auth/action.yml",
".github/workflows/build-docker-image.yaml",
".github/workflows/build-installer.yaml",
".github/workflows/release.yaml",
".readthedocs.yaml",
"docs/Makefile",
"docs/_static/.gitkeep",
"docs/conf.py",
"docs/index.rst",
"docs/make.bat",
"docs/requirements.txt",
"pyrightconfig.json",
"src/entrypoint.py",
"tests/unit/__init__.py",
"tests/unit/conftest.py"
"backend/.python-version",
"backend/kiota_nullable_fixer.py",
"backend/pyinstaller.spec",
"backend/pyproject.toml",
"backend/src/__init__.py",
"backend/src/backend_api/__init__.py",
"backend/src/backend_api/app_def.py",
"backend/src/backend_api/app_runner.py",
"backend/src/backend_api/camel_case_model.py",
"backend/src/backend_api/entrypoint/__init__.py",
"backend/src/backend_api/entrypoint/cli.py",
"backend/src/backend_api/entrypoint/crash_dump.py",
"backend/src/backend_api/entrypoint/grant_rights.py",
"backend/src/backend_api/entrypoint/parser.py",
"backend/src/backend_api/entrypoint/service_install_password.py",
"backend/src/backend_api/entrypoint/win_service.py",
"backend/src/backend_api/fast_api_exception_handlers.py",
"backend/src/backend_api/jinja_constants.py",
"backend/src/backend_api/logger_config.py",
"backend/src/backend_api/openapi_problem_responses.py",
"backend/src/backend_api/openapi_schema_simplifier.py",
"backend/src/entrypoint.py",
"backend/src/static/.gitignore",
"backend/src/static/favicon.ico",
"backend/tests/__init__.py",
"backend/tests/conftest.py",
"backend/tests/e2e/__init__.py",
"backend/tests/e2e/app_bootup.py",
"backend/tests/e2e/conftest.py",
"backend/tests/e2e/generated/open_api/backend/api/api_request_builder.py",
"backend/tests/e2e/generated/open_api/backend/api/healthcheck/healthcheck_request_builder.py",
"backend/tests/e2e/generated/open_api/backend/api/shutdown/shutdown_request_builder.py",
"backend/tests/e2e/generated/open_api/backend/backend_client.py",
"backend/tests/e2e/generated/open_api/backend/models/h_t_t_p_validation_error.py",
"backend/tests/e2e/generated/open_api/backend/models/healthcheck_response.py",
"backend/tests/e2e/generated/open_api/backend/models/problem_details.py",
"backend/tests/e2e/generated/open_api/backend/models/shutdown_response.py",
"backend/tests/e2e/generated/open_api/backend/models/validation_error.py",
"backend/tests/e2e/jinja_constants.py",
"backend/tests/unit/__init__.py",
"backend/tests/unit/__snapshots__/test_basic_server_functionality/test_openapi_schema.json",
"backend/tests/unit/conftest.py",
"backend/tests/unit/entrypoint/__init__.py",
"backend/tests/unit/entrypoint/fixtures.py",
"backend/tests/unit/entrypoint/test_arg_parsing.py",
"backend/tests/unit/entrypoint/test_crash_dump.py",
"backend/tests/unit/entrypoint/test_entrypoint.py",
"backend/tests/unit/entrypoint/test_grant_rights.py",
"backend/tests/unit/entrypoint/test_service_install_password.py",
"backend/tests/unit/entrypoint/test_service_subcommand.py",
"backend/tests/unit/entrypoint/test_stopping_behavior.py",
"backend/tests/unit/snapshot.py",
"backend/tests/unit/spy_helpers.py",
"backend/tests/unit/test_basic_server_functionality.py",
"backend/tests/unit/test_camel_case_model.py",
"backend/tests/unit/test_fast_api_exception_handlers.py",
"backend/tests/unit/test_openapi_problem_responses.py",
"backend/tests/windows_service/__init__.py",
"backend/tests/windows_service/conftest.py",
"backend/tests/windows_service/test_service_lifecycle.py",
"deployment/deploy.bat",
"deployment/docker-compose.yaml",
"frontend/app/app.config.ts",
"frontend/app/assets/css/main.css",
"frontend/app/layouts/default.vue",
"frontend/app/pages/index.vue",
"frontend/eslint.config.mjs",
"frontend/eslint.shared.mjs",
"frontend/kiota_nullable_fixer.py",
"frontend/nuxt.config.ts",
"frontend/package.json",
"frontend/playwright.config.ts",
"frontend/scripts/postinstall.mjs",
"frontend/tests/compiled/index.spec.ts",
"frontend/tests/e2e/application-behavior/backend-behavior.spec.ts",
"frontend/tests/e2e/compose.ts",
"frontend/tests/e2e/fixtures.ts",
"frontend/tests/e2e/global-setup.ts",
"frontend/tests/e2e/global-teardown.ts",
"frontend/tests/e2e/helpers/vrt-masks.ts",
"frontend/tests/e2e/helpers/vrt.ts",
"frontend/tests/e2e/index.spec.ts",
"frontend/tests/e2e/ports.ts",
"frontend/tests/setup/auto-unmount.ts",
"frontend/tests/setup/bun-test-stub.ts",
"frontend/tests/setup/constants.ts",
"frontend/tests/setup/faker.ts",
"frontend/tests/unit/eslint-rules/istanbul-ignore-if-must-throw.spec.ts",
"frontend/tests/unit/pages/default-layout.nuxt.spec.ts",
"frontend/tests/unit/pages/index.nuxt.spec.ts",
"frontend/tsconfig.json",
"frontend/types/nuxt.config.d.ts",
"frontend/vitest.config.ts",
"installer/.gitignore",
"installer/ARCHITECTURE.md",
"installer/INSTALL.txt",
"installer/README.md",
"installer/license.rtf",
"installer/package.ps1",
"installer/scripts/install-service.ps1",
"installer/scripts/remove-service.ps1",
"installer/stage.ps1",
"installer/wix/Product.wxs"
]
}
]
Expand Down
138 changes: 138 additions & 0 deletions .config/eslint-rules/istanbul-ignore-if-must-throw.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* ============== WARNING ==============================================================================
* File is managed by copier template: gh:LabAutomationAndScreening/copier-nuxt-python-intranet-app.git
* See .config/.copier-managed-files.json for details.
*
* You are welcome to make changes to this file in your repo if they are custom to your project,
* but if the change should be shared with other projects, please backport it to the template repo.
* =====================================================================================================
*/
/*
* Enforces the defensive-assertion contract for coverage-ignored branches:
*
* A branch marked `istanbul ignore if` (or `istanbul ignore next` when it sits on
* an `if`) must throw: per ESLint's code path analysis, the end of the branch is
* unreachable and no reachable return/break/continue inside it targets a construct
* outside it. Coverage-ignoring a guard means "this is unreachable"; a silent
* `return` there hides a real bug instead of surfacing it. If the absence is a
* legitimate state rather than a contract violation, the author opts out with
* `return-ok <reason>` in the ignore comment (a colon after the token is optional) —
* "cannot happen" is what the throw is for, not what the escape is for. The reason is
* required rather than conventional, since a bare `return-ok` is indistinguishable
* from silencing the guard.
* Ignore comments on anything other than an `if` are left alone — the rule
* only makes a claim about branches whose shape it can verify.
*/

const IGNORE_IF_OR_NEXT = /istanbul ignore (?:if|next)\b/;
// `return-ok` contains a hyphen, so `\b` would accept one as a boundary and read the token out of
// `not-return-ok`; the delimiter must exclude hyphens on both sides.
const RETURN_OK = /(?<![-\w])return-ok(?![-\w])/;
// The reason may not start with the colon, or `return-ok:` would backtrack into being its own reason.
const RETURN_OK_WITH_REASON = /(?<![-\w])return-ok(?![-\w]):?\s*[^\s:]/;

const LOOP_TYPES = new Set(["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"]);
const SILENT_EXIT_TYPES = new Set(["ReturnStatement", "BreakStatement", "ContinueStatement"]);

function isExitTarget(exit, ancestor) {
if (exit.label !== null) {
return ancestor.type === "LabeledStatement" && ancestor.label.name === exit.label.name;
}
if (exit.type === "BreakStatement") return LOOP_TYPES.has(ancestor.type) || ancestor.type === "SwitchStatement";
return LOOP_TYPES.has(ancestor.type);
}

function leavesBranch(exit, branch) {
if (exit.type === "ReturnStatement") return true;
for (let cur = exit.parent; cur !== branch.parent; cur = cur.parent) {
if (isExitTarget(exit, cur)) return false;
}
return true;
}

/** @type {import("eslint").Rule.RuleModule} */
export default {
meta: {
type: "problem",
docs: {
description:
"Require `istanbul ignore if` branches to throw a defensive assertion naming the violated invariant",
},
schema: [],
messages: {
mustThrow:
'`istanbul ignore if` claims this branch cannot be reached, so it must throw and name the invariant that was violated — a silent exit hides exactly the bug the guard exists to catch. `return-ok` is not a shortcut for "cannot happen": use it only when the absence is a legitimate state the code tolerates, and say in the comment why it is legitimate.',
escapeNeedsReason:
"A `return-ok` escape must say why the absence is legitimate, as `return-ok <reason>`. The escape claims this branch is a state the code tolerates rather than a violated invariant, and that claim is the reviewer's only evidence the guard was not simply silenced.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const segmentStacks = [];
// Ignored ifs can nest; depth-first traversal keeps the innermost one on top.
const branchStack = [];

function isReachable() {
return [...segmentStacks.at(-1)].some((segment) => segment.reachable);
}

function report(entry) {
if (entry.reported) return;
entry.reported = true;
context.report({ node: entry.node, messageId: "mustThrow" });
}

function onSilentExit(node) {
const entry = branchStack.at(-1);
// Depth check keeps exits inside a nested function (its own code path) from counting.
if (entry === undefined || entry.depth !== segmentStacks.length) return;
if (isReachable() && leavesBranch(node, entry.consequent)) report(entry);
}

return {
onCodePathStart() {
segmentStacks.push(new Set());
},
onCodePathEnd() {
segmentStacks.pop();
},
onCodePathSegmentStart(segment) {
segmentStacks.at(-1).add(segment);
},
onCodePathSegmentEnd(segment) {
segmentStacks.at(-1).delete(segment);
},
onUnreachableCodePathSegmentStart(segment) {
segmentStacks.at(-1).add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
segmentStacks.at(-1).delete(segment);
},
IfStatement(node) {
const leading = sourceCode.getCommentsBefore(node);
const ignoreComment = leading.find((comment) => IGNORE_IF_OR_NEXT.test(comment.value));
if (ignoreComment === undefined) return;
if (RETURN_OK.test(ignoreComment.value)) {
if (RETURN_OK_WITH_REASON.test(ignoreComment.value)) return;
context.report({ node, messageId: "escapeNeedsReason" });
return;
}
branchStack.push({ node, consequent: node.consequent, depth: segmentStacks.length, reported: false });
},
":statement:exit"(node) {
if (node !== branchStack.at(-1)?.consequent) return;
const entry = branchStack.pop();
// ESLint applies a throw/return/break/continue to the code path only after emitting that
// node's :exit, so for a braceless consequent the segments seen here are still the ones
// from before it. A bare throw satisfies the rule; a bare silent exit was already judged
// by onSilentExit on entry.
const { type } = entry.consequent;
if (type === "ThrowStatement" || SILENT_EXIT_TYPES.has(type)) return;
if (isReachable()) report(entry);
},
ReturnStatement: onSilentExit,
BreakStatement: onSilentExit,
ContinueStatement: onSilentExit,
};
},
};
3 changes: 2 additions & 1 deletion .config/pyrefly.mutmut.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
use-ignore-files = false
disable-project-excludes-heuristics = true
python-platform = "linux"
project-includes = ["../mutants/src/**"]
preset = "strict"
project-includes = ["../backend/mutants/src/**"]
project-excludes = [
# paths are relative to this config file (in .config/), so prefix with ../ to anchor at the repo root
"../**/generated/**",
Expand Down
Loading