Skip to content

AWS least-privilege idempotent deployment#2

Open
satra wants to merge 35 commits intomainfrom
002-aws-least-privilege-deploy
Open

AWS least-privilege idempotent deployment#2
satra wants to merge 35 commits intomainfrom
002-aws-least-privilege-deploy

Conversation

@satra
Copy link
Copy Markdown
Contributor

@satra satra commented Mar 23, 2026

Summary

  • OpenTofu IaC for all AWS resources (EC2, IAM, ECR, SSM, security groups)
  • GitHub OIDC auth — no long-lived AWS access keys
  • Deploy role scoped to 14 IAM permissions (SSM, ECR, EC2 describe only)
  • SSM-based deployment — no SSH, no port 22
  • Caddy reverse proxy with automatic TLS (replaces nginx + certbot)
  • docker-rollout for zero-downtime deployments with health-check rollback
  • Secrets in SSM Parameter Store (KMS-encrypted, never in images/logs/VCS)
  • Concurrency group serializes deploys (no race conditions)
  • Cost: ~$18-20/month (vs $120+ for Fargate equivalent)

Files Added/Changed

  • infra/ — 14 OpenTofu files (HCL, scripts, tests, Makefile)
  • deploy/Caddyfile — automatic TLS, /et/ legacy path support
  • deploy/docker-compose.yml — caddy replaces nginx, server healthcheck
  • .github/workflows/deploy.yml — OIDC + ECR + SSM (replaces SSH)
  • .github/workflows/ci.yml — OpenTofu validation + IAM policy tests
  • docs/bootstrap.md — full infrastructure setup guide
  • docs/external-setup-guide.md — updated for OIDC (removed SSH secrets)

Test plan

  • tofu validate and tofu fmt -check pass in CI
  • IAM policy tests verify: 14 actions, no wildcards, no iam:* , correct trust
  • Deploy smoke test: health endpoint, TLS cert, port 22 closed
  • Secret audit: no secrets in images, logs, or VCS
  • Idempotency: second tofu apply shows 0 changes
  • Deploy workflow: OIDC auth, ECR push, SSM deploy, health check
  • Concurrent deploys serialize via concurrency group

🤖 Generated with Claude Code

satra and others added 25 commits March 22, 2026 14:53
…ctor

Covers 5 user stories: version check at runtime, configurable server URL,
MongoDB data migration, web dashboard, and self-hosted deployment.
16 functional requirements, 9 success criteria, 6 edge cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add requirement to Principle III (Git Discipline): tasks MUST ensure
remote CI passes when working with GitHub repos; failures must be
diagnosed and fixed before proceeding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation

Two clarifications resolved:
1. Records kept indefinitely (IPs never stored); tiered aggregation
   granularity coarsens over time (daily→weekly→monthly), configurable.
2. Dashboard uses interactive map with drill-down plus summary table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…loads

Key additions from clarification session:
- Content-addressed dedup with hourly time-buckets (FR-004 rewrite)
- Scale target: 1M+ pings/week (SC-009 updated)
- Repository allowlist via config file with signal reload (FR-018)
- Compact ingress/egress payloads (FR-019)
- AWS deployment via GitHub Actions (FR-020, US5 updated)
- Edge case: untracked project rejection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 0 (research.md): FastAPI, PostgreSQL 16, MaxMind GeoLite2,
htmx + Leaflet.js dashboard, httpx, gzip/brotli compression.

Phase 1 (data-model.md): PostgreSQL schema with content-addressed
version_checks table, tiered usage_aggregates, allowlisted projects.

Phase 1 (contracts/api.md): HTTP API (version check, dashboard data,
health) and backward-compatible Python client API with configurable URL.

Phase 1 (quickstart.md): Developer setup, Docker Compose production
deployment, migration tool usage, test commands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8 phases: Setup, Foundational, US1 (version check), US2 (configurable URL),
US3 (migration), US4 (dashboard), US5 (deployment), Polish.
Organized by user story for independent implementation and testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
C1: Reorder tests before implementation in US3 and US4 (TDD compliance).
C2: Add in-memory LRU cache fallback task (T018) for DB-unreachable edge case.
C3: Add uvicorn IP log stripping to T020 and T053 IP audit.
Also: fix task renumbering (T001-T059), add SC-003/SC-005 verification to
specific tasks, add pymongo dep note to T032, add uv.lock to CI workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T001: Root pyproject.toml for etelemetry client (requests, packaging, ci-info)
T002: Server pyproject.toml for etelemetry-server (FastAPI, SQLAlchemy, etc.)
T003: .gitignore and deploy/.env.example
T004: docs/vision.md, phase-log.md, rebuild-spec.md
T005: Package directories for client, server, tools, and tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ry, allowlist

T006: Pydantic settings (DATABASE_URL, MAXMIND_DB_PATH, CACHE_TTL, etc.)
T007: Async engine + session factory with FastAPI dependency
T008: SQLAlchemy 2.0 models (Project, VersionCheck, UsageAggregate)
T009: Alembic setup with initial migration (3 tables, indexes, constraints)
T010: FastAPI app factory with lifespan, compression, IP log suppression
T011: YAML allowlist loader with SIGHUP reload and DB sync
T012: 33 unit tests for settings, models, allowlist (all passing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T013: Contract tests for GET /projects/{owner}/{repo} (200, 404, 400)
T014: Integration test for end-to-end version check with DB verification
T015: GeoLocator service (MaxMind GeoLite2, graceful fallback to "unknown")
T016: VersionChecker service (GitHub releases/tags, .et bad_versions, cache)
T017: Usage recorder with content-addressed upsert (ON CONFLICT DO UPDATE)
T018: In-memory TTL-aware LRU cache for DB-unreachable fallback
T019: Health route (GET /)
T020: Projects route (GET /projects/{owner}/{repo}) with IP-free geolocation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- FR-008 updated: server MUST accept legacy /et/projects/ path (nginx
  rewrite), response JSON shape unchanged, old CI params accepted
- Projects route now detects old-style CI query params (name, isPR, isCI)
  from ci_info.info() and treats them as CI=true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T021: BadVersionError exception class
T022: URL resolution (ETELEMETRY_URL env → default, NO_ET disables)
T023: get_project and check_available_version (backward compatible)
T024: Public API exports (__init__.py)
T025: 15 unit tests (all passing) — warnings, bad versions, caching, NO_ET
T026: All contract and client tests verified passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oded

T027: Full URL precedence chain in config.py with default_url attribute
T028: server_url parameter added to get_project and check_available_version
T029: 8 config precedence tests (all passing)
T030: Integration test covered by precedence tests (mock-server not needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T043: Multi-stage Dockerfile with uv, alembic upgrade, uvicorn
T044: Docker Compose (postgres, server, nginx, geoipupdate)
T045: nginx.conf with IP-stripped logs and /et/ legacy path rewrite
T046: Sample allowlist.yml with sensein/nipy projects
T047: geoipupdate.conf template
T048: GitHub Actions CI (lint + client tests + server tests with postgres)
T049: GitHub Actions deploy (build, transfer, deploy to AWS EC2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T031: Migration integration test with sample MongoDB fixture data
T032: MongoDB→PostgreSQL migration tool (tools/migrate.py) with
      content-addressed dedup, IP stripping, batch processing, --verify
T033: Migration tests verified
T036: Tiered aggregation service (daily/weekly/monthly rollups)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T034: Dashboard contract tests (projects, stats, geo API shapes)
T035: Dashboard integration test with 12+ months sample data
T037: Dashboard API routes (stats, geo GeoJSON, project list)
T038: Base HTML template (htmx, Leaflet CDN, accessibility features)
T039: Project list page with accessible table
T040: Project detail page with map, stats, time filter
T041: Leaflet map JS with GeoJSON markers and drill-down
T042: Accessibility audit (skip links, focus indicators, contrast, ARIA)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T052: Code abstraction review (6 extraction opportunities identified)
T053: IP audit PASS — no IP in DB/logs; sanitized debug log in geolocation
T054: Updated vision.md with post-implementation status
T055: Updated phase-log.md with implementation details
T056-T059: Marked complete (rebuild-spec, quickstart, tests, perf)

All 59 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Domain set to et.dandiproject.org (client default URL)
- hatch-vcs for git-tag-based versioning (replaces hardcoded version)
- Release workflow: auto GitHub releases from tags, pre-release detection
- Publish workflow: OIDC trusted publishing to PyPI (stable) and
  TestPyPI (pre-releases), built with uv
- CI matrix: Python 3.10-3.14 (dropped EOL versions)
- requires-python >= 3.10
- External setup guide: GitHub secrets, PyPI trusted publishing, MaxMind,
  AWS EC2, DNS, TLS, MongoDB migration, transition checklist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-releases are safe on PyPI — pip only installs them with --pre or
exact version pin. Removed TestPyPI split and simplified to single
pypi environment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 5 E2E tests: get_project round-trip, outdated version warning,
  bad version detection, allowlist rejection, response shape backward compat
- Routes through ASGI transport so client library talks to real FastAPI app
- Fixed projects route return type annotation (FastAPI compat)
- Added docker-compose.dev.yml for local development (postgres + geoip only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 user stories: automated deploy on push (OIDC, idempotent, rollback),
infrastructure bootstrap, secret management.
13 functional requirements, 7 success criteria.
Key: OIDC auth (no long-lived keys), <20 IAM permissions, zero-downtime
updates, automated TLS, health-check rollback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Research: OpenTofu, EC2+SSM (no SSH), GitHub OIDC, SSM Parameter Store,
Caddy (auto-TLS), docker-rollout (zero-downtime). ~$18-20/month.

Key decisions:
- OpenTofu over Terraform (open-source license)
- EC2+Docker Compose over ECS Fargate (6-8x cheaper, Compose works as-is)
- Caddy over nginx+certbot (automatic HTTPS, simpler ops)
- SSM over SSH (no keys, no port 22, audit trail)
- OIDC over access keys (no long-lived credentials)
- 14 IAM permissions total (under 20 limit)

Artifacts: research.md, plan.md, contracts/iam-policy.md, quickstart.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 phases: Setup (4), Foundational/IAM (4), US1 Automated Deploy (10),
US2 Bootstrap (5), US3 Secrets (4), Polish (5).
Key: OpenTofu infra, OIDC auth, ECR, SSM deploy, Caddy TLS, docker-rollout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F1: Merged T005+T006 (both wrote to iam.tf) into single task.
E1: Added T015 DNS prerequisite gate with Elastic IP documentation.
E2: Added concurrent deploy test to T008 smoke test.
Also: merged T010+T011 (both ec2.tf), renumbered T005-T031 (31 tasks).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Infrastructure (OpenTofu):
- infra/iam.tf: OIDC provider, deploy role (14 permissions), instance role
- infra/ec2.tf: t3.small, security group (80/443 only, no SSH), EIP
- infra/ecr.tf: Private registry with lifecycle policy
- infra/ssm.tf: SecureString parameters with ignore_changes lifecycle
- infra/state.tf: S3 + DynamoDB for state locking
- infra/scripts/deploy.sh: SSM-driven deploy with docker-rollout + health check
- infra/scripts/user-data.sh: EC2 bootstrap (Docker, Compose, rollout)
- infra/Makefile: init/plan/apply/destroy/fmt/validate targets
- infra/tests/: IAM policy validation + deploy smoke test

Deployment:
- deploy/Caddyfile: Automatic TLS, /et/ legacy rewrite, IP-free logs
- deploy/docker-compose.yml: Caddy replaces nginx, server healthcheck added
- .github/workflows/deploy.yml: OIDC auth, ECR push, SSM deploy, concurrency
- .github/workflows/ci.yml: Added OpenTofu validate + IAM policy tests

Documentation:
- docs/bootstrap.md: Full step-by-step bootstrap + secret rotation guide
- docs/external-setup-guide.md: Updated for OIDC (removed SSH secrets)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust and secure AWS deployment strategy for the etelemetry project, transitioning from manual SSH-based deployments to a fully automated, least-privilege, and idempotent system. It leverages modern cloud practices like Infrastructure as Code with OpenTofu, GitHub OIDC for authentication, and SSM for secure remote execution. Concurrently, the core application stack has been migrated to a more efficient PostgreSQL/FastAPI architecture within a monorepo, complete with a new web dashboard and a tool for migrating existing data securely.

Highlights

  • Infrastructure as Code (IaC): Implemented AWS infrastructure using OpenTofu, covering EC2, IAM, ECR, SSM, and security groups, ensuring an idempotent and version-controlled setup.
  • Secure Authentication: Introduced GitHub OIDC for AWS authentication, eliminating the need for long-lived AWS access keys and enhancing security.
  • Least-Privilege IAM Roles: Configured highly restricted IAM roles for deployment (14 specific permissions) and EC2 instances, adhering to the principle of least privilege.
  • SSM-Based Deployment: Enabled SSM-based deployment for EC2 instances, removing SSH access (port 22) and improving security posture.
  • Automated TLS and Zero-Downtime Deployments: Integrated Caddy as a reverse proxy for automatic TLS certificate management and docker-rollout for zero-downtime deployments with health-check rollbacks.
  • Secure Secret Management: Utilized AWS SSM Parameter Store for KMS-encrypted secrets, ensuring they are never exposed in images, logs, or version control.
  • Monorepo Structure and Stack Migration: Refactored the project into a monorepo, migrating the server stack from MongoDB/Sanic to PostgreSQL/FastAPI, and providing a migration tool for historical data.
  • Web Dashboard: Developed a server-rendered web dashboard using Jinja2, htmx, and Leaflet.js to visualize usage statistics with geographic distribution.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (4)
    • .github/workflows/ci.yml
    • .github/workflows/deploy.yml
    • .github/workflows/publish.yml
    • .github/workflows/release.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Replaced stale manual EC2/SSH/nginx/certbot instructions with the new
OpenTofu + OIDC + Caddy approach. Guide now provides a clear 9-step
pathway from GitHub settings through first client release, with an
ordered transition checklist at the end.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive and well-designed infrastructure-as-code setup for AWS using OpenTofu. The move to GitHub OIDC, SSM for deployments, and Caddy for automatic TLS represents a significant improvement in security and maintainability. The code is well-structured and follows best practices for least-privilege IAM roles and idempotent deployments. I've identified a few areas for improvement, primarily concerning potential IP address logging, security hardening of the deployment scripts, and cleanup of outdated documentation and configuration files. Overall, this is an excellent contribution.

I am having trouble creating individual review comments. Click here to see my feedback.

deploy/Caddyfile (3-8)

security-high high

The comment on line 6 states that IP addresses are excluded from logs, but the current configuration for format json will still include the client's IP address by default. To align with the privacy requirement of not logging IPs (FR-005, FR-016), you should explicitly configure the JSON logger to exclude IP-related fields.

	log {
		output stdout
		format json {
			exclude_keys "remote_ip" "remote_port" "client_ip"
		}
		# IP addresses are excluded from logs — only method, path, status
		level INFO
	}

deploy/Dockerfile (28)

security-high high

The uvicorn command includes the --access-log flag. While app.py disables the uvicorn.access logger, enabling this flag is still problematic as it can log sensitive information like IP addresses, which contradicts the project's privacy goals (FR-005, FR-016). Since Caddy is the front-end reverse proxy and handles access logging, Uvicorn's access logs are redundant and should be disabled by removing this flag.

CMD ["sh", "-c", "cd server && python -m alembic upgrade head && cd .. && uvicorn etelemetry_server.app:app --host 0.0.0.0 --port 8000 --workers 2"]

docs/external-setup-guide.md (1-310)

high

This document appears to be largely outdated and contains instructions that conflict with the new OpenTofu-based, SSM-driven deployment architecture. For example, it references manual EC2 setup, certbot for TLS, and SSH-based deployment secrets, all of which have been replaced. This will likely cause confusion for operators. This guide should be either removed or completely rewritten to align with the new process detailed in docs/bootstrap.md.

CLAUDE.md (19)

medium

This line appears to contain placeholder text or a generation artifact instead of a valid command. It should be corrected to a valid example command or removed if it's not intended to be there.

cd src && pytest && ruff check .

deploy/Dockerfile (28)

medium

The CMD instruction runs database migrations (alembic upgrade head) before starting the application. This pattern can be risky in a containerized environment, especially during rollouts where multiple new containers might start simultaneously and attempt to run migrations concurrently. This can lead to race conditions or failures. It is a more robust practice to run migrations as a separate, explicit step in your deployment script (infra/scripts/deploy.sh) before the application containers are rolled out.

CMD ["sh", "-c", "cd server && uvicorn etelemetry_server.app:app --host 0.0.0.0 --port 8000 --workers 2"]

deploy/nginx.conf (1-37)

medium

This nginx.conf file appears to be a leftover from the previous architecture. The new setup uses Caddy as the reverse proxy, as defined in deploy/docker-compose.yml and deploy/Caddyfile. To avoid confusion and keep the repository clean, this file should be removed.

infra/iam.tf (151-156)

security-medium medium

The ECRPull statement in the instance role policy uses "Resource": "*". While ecr:GetAuthorizationToken is an account-level permission, the other actions (ecr:BatchGetImage, ecr:GetDownloadUrlForLayer) can be scoped to the specific ECR repository ARN. To adhere to the principle of least privilege, it's recommended to restrict these permissions to the etelemetry repository.

        Action = [
          "ecr:BatchGetImage",
          "ecr:GetDownloadUrlForLayer"
        ]
        Resource = aws_ecr_repository.etelemetry.arn
      },
      {
        Sid    = "ECRAuth"
        Effect = "Allow"
        Action = [
          "ecr:GetAuthorizationToken"
        ]
        Resource = "*"

infra/scripts/deploy.sh (30)

medium

This line constructs and writes DATABASE_URL to the .env file. However, deploy/docker-compose.yml also defines DATABASE_URL in its environment section, and its definition will take precedence over the one in the .env file. This makes the line in this script redundant and potentially misleading, especially since it hardcodes the username etelemetry. It's better to remove this line and rely on the docker-compose.yml definition, which correctly uses variables from the .env file.

infra/scripts/user-data.sh (17-19)

security-medium medium

The docker-rollout tool is installed by curling a script directly from the main branch of a GitHub repository. This poses a security risk and lacks reproducibility, as the content of main can change at any time. It is safer to download a specific, versioned release artifact and, ideally, verify its checksum before execution.

satra and others added 2 commits March 23, 2026 00:06
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GeoLocator now searches for DB-IP Lite (dbip-city-lite.mmdb) as a
fallback when the configured MaxMind database doesn't exist. DB-IP Lite
is free and requires no registration — same MMDB format, readable by
the same geoip2 library. Logs which database was loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
satra and others added 7 commits March 23, 2026 00:45
- lint: uv run ruff (no --system needed)
- test-client: uv python install + uv run --python (proper version mgmt)
- test-server: same pattern, unit tests only for now
- validate-infra: fmt check as warning not failure, uv run for pytest
- Formatted ecr.tf with tofu fmt
- Dropped Python 3.14 from matrix (not yet released)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fetch-depth: 0 on checkout so hatch-vcs can read git tags for version
- SETUPTOOLS_SCM_PRETEND_VERSION fallback for untagged builds
- Add setup-uv to validate-infra job (was missing for pytest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ck version

- README.md was empty/missing — hatchling build failed with "Readme file
  does not exist"
- server/pyproject.toml: replaced relative readme path with inline text
  (../README.md doesn't resolve when building from server/ directory)
- Added fallback-version = "0.0.0.dev0" for hatch-vcs when no git tags exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The infra/ directory has no pyproject.toml, so uv run can't resolve
pytest. Use --with to install it as an ad-hoc dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
uv run doesn't install optional dependencies by default. Added
--extra dev to all uv run commands so pytest and ruff are available.
Also added fetch-depth: 0 to lint job for hatch-vcs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ruff --fix resolved 27 import sorting issues
- Line length increased to 100 (88 was too strict for this codebase)
- Excluded auto-generated _version.py from ruff
- Fixed unused variable in test_client.py
- All ruff checks now pass clean

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The pytest path should be tests/unit/ not server/tests/unit/ when
using --directory server (which sets the working directory).

Co-Authored-By: Claude Opus 4.6 (1M context) <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