Skip to content

v7: correct SOPS backend wording and ADR citations - #1

Open
andrewsauder wants to merge 17 commits into
mainfrom
claude/gcgov-deploy-implementation-ixsu2i
Open

v7: correct SOPS backend wording and ADR citations#1
andrewsauder wants to merge 17 commits into
mainfrom
claude/gcgov-deploy-implementation-ixsu2i

Conversation

@andrewsauder

Copy link
Copy Markdown
Member

This branch is v7 plus two documentation corrections from the "truthful and armed" review, so merging it is the v7 → main merge for this repository. Merge after gcgov/framework.

What changed

  • DOCKER.md — replaced the stale "GCP KMS key per Zone" wording with Azure Key Vault (one vault per Zone), and pointed the SOPS citations at the moved ADRs: gcgov/deploy docs/adr/0001-… (secrets never decrypt) and gcgov/deploy docs/adr/0004-… (Azure Key Vault).
  • LOCAL-DEVELOPMENT.md — updated the replica-set ADR link to framework's renumbered 0004-writes-are-transactional-so-mongodb-is-a-replica-set.md.

One ordering note

The LOCAL-DEVELOPMENT.md link targets github.com/gcgov/framework/blob/main/docs/adr/0004-…. That file only exists on main after the gcgov/framework PR merges — so the link resolves once framework's v7 → main lands (step 2 of the merge order), not before.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NzByhoDp7hsD39aoThJ9rv


Generated by Claude Code

claude and others added 17 commits August 16, 2026 02:30
Replace the Windows/IIS hosting with a Linux container stack and inject
secrets from the environment / Docker secrets via the framework's
%env(...)% config resolver.

Docker:
- Dockerfile: multi-stage (base -> vendor/dev/prod) PHP 8.3-FPM with
  ext-mongodb/sodium/zip; `prod` is the default target.
- docker-compose.yml: php + nginx, optional mongodb behind the `dev`
  profile; CORS origins and PHP-FPM host from the environment.
- docker/nginx/default.conf.template: nginx translation of the five IIS
  web.config behaviors — scheme from X-Forwarded-Proto (TLS at the edge),
  trailing-slash strip, static pass-through (theme/, favicon.ico), front
  controller to index.php (REQUEST_URI preserved), and CORS with an origin
  allowlist + preflight. Rendered by the stock nginx image's envsubst.
- .env.example (+ .dockerignore); .gitignore now tracks composer.json /
  composer.lock and ignores .env, .env.local, /secrets/.

Config -> %env():
- environment-local.json: dev-safe %env(default:...)% fallbacks
  (e.g. MONGO_URI defaults to mongodb://mongodb:27017); phpPath emptied.
- environment-prod.json: hard %env(VAR)% references (no defaults) so a
  missing secret fails loudly, naming the variable. File-based secret
  alternative (%env(trim:file:VAR_FILE)%) documented in DOCKER.md.
- app.json: SMTPUsername/SMTPPassword via %env(default::...)%.
- Non-secret identity/URL fields keep their {app_*}/{prod_app_*} gf setup
  tokens.

Removals (Windows/IIS): www/web-*.config, app/cli/*.bat, srv/app.* php.ini
trees, scripts/*.ps1, update-production.ps1, composer-local.json,
composer-prod.json.

Other:
- www/index.php: __DIR__-relative autoload; drop the removed constructor arg
  (framework::__construct() takes none).
- composer.json: committed; php >=8.3; gcgov/framework bumped to ^v6.2 (the
  release that must carry the %env() resolver — final tag left to the owner).
- composer-ci.json + ci.yml: point the framework path-repo/checkout at the
  Docker-support branch until it is tagged/merged; `rm -f composer.lock`
  before path-repo install; new nginx-config-lint and (best-effort)
  prod docker-build CI jobs.
- README.md/DOCKER.md: Docker quick start, gf setup/env flow, and a guide to
  setting environment variables securely (Docker/Swarm/Kubernetes secrets,
  TLS at the edge).

Note: composer.lock is not committed here because the framework release tag
and the sibling plugin repos are unavailable in this environment; generate and
commit it once the framework %env() release is tagged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru
Companion to gcgov/framework v7: the gf CLI no longer reads committed
environment-{env}.json variants, so the template ships ONE committed
app/config/environment.json and the environment is decided entirely by
which variable values the process is given.

- environment-local.json / environment-prod.json deleted; environment.json
  committed (removed from .gitignore). Identity fields use the
  token-as-default pattern (%env(default:{app_*}:VAR)% — `gf setup` bakes
  the dev value, env vars override per environment); secrets and Mongo
  coordinates are hard %env(VAR)% references that fail loudly when unset.
  This also fixes the clean-checkout Docker image shipping no
  environment.json at all (it was gitignored and only created by the old
  `gf env` copy step).
- rootUrl AND basePath are now per-environment (APP_ROOT_URL,
  APP_BASE_PATH): app/router.php derives its route prefix at runtime from
  config::getEnvironmentConfig()->getBasePath() instead of a baked
  {app_base_path} token, and jwtAuth tokenIssuedBy/tokenPermittedFor
  reference the same variables so they can never desync from routing.
  RouterTest updated to the runtime-derived /api prefix.
- New app/config/prod.env.example: copy to gitignored app/config/prod.env
  to enable `gf db:restore --from=prod` / `gf db:run --env=prod` /
  `gf env prod` (variant overlay reads). APP_TYPE=prod feeds the
  db:restore prod guard. .gitignore/.dockerignore exclude
  app/config/*.env so a real prod.env never reaches git or image layers.
- .env.example: commented identity overrides; new
  tests/Unit/ConfigFilesTest.php pins that every hard %env() reference in
  environment.json/app.json is covered by .env.example and
  prod.env.example (the completeness contract).
- composer.json: gcgov/framework ^v7.0. CI docker-build job asserts the
  built image contains app/config/environment.json.
- README.md / DOCKER.md rewritten for the no-activation model, including
  a production configuration checklist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru
Companion to gcgov/framework v7's unified-config change: the app ships ONE
committed config file at the application root, and the per-variant overlay
files live at the root too.

- New /config.json merges the former app/config/app.json (app, email,
  settings sections) and app/config/environment.json (everything else);
  app/config/ is deleted. Secrets and Mongo coordinates stay hard %env()
  references; identity values keep the token-as-default pattern.
- prod.env.example moves to the application root (overlay files are now
  {root}/{name}.env), and gains SMTP_USERNAME/SMTP_PASSWORD entries so the
  example stays a complete variable inventory.
- Root-level placement verified collision-free before choosing it over a
  /config directory: docker compose reads only .env, glob('*.env')
  excludes dotfiles and *.env.example, and the gitignore (/*.env) and
  dockerignore (*.env) patterns keep real overlays out of git and image
  layers while prod.env.example stays committed.
- app/router.php uses the flattened config::getBasePath() accessor
  (config::getEnvironmentConfig() no longer exists in v7).
- tests/bootstrap.php seeds the new unifiedConfig; ConfigFilesTest pins
  the unified file against both example env files (including the merged
  app/email/settings sections). CI docker smoke asserts /config.json in
  the built image. README/DOCKER.md/.env.example updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru
Companion to the framework redesign: the gitignored prod.env overlay file
is replaced by a committed, CLI-only `environments.prod` entry inside
config.json.

- config.json gains `environments.prod` (type literal "prod" +
  mongoDatabases referencing PROD_MONGO_URI / PROD_MONGO_DATABASE); the
  runtime strips this section, so those variables need not be set to run.
- prod.env.example deleted; .env.example documents the commented PROD_*
  variables (used by gf db:restore --from=prod / db:run --env=prod) — the
  prefix means a missing value fails loudly instead of resolving to the
  local MONGO_URI.
- .gitignore drops the obsolete /*.env overlay rule (only .env/.env.local
  remain).
- ConfigFilesTest seeds the environment from .env.example (mirroring
  cp .env.example .env) and pins: the active config resolves without any
  PROD_* set, and environments.prod resolves once PROD_* are supplied.
- README/DOCKER updated to the environments-section workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru
The template's CI could not run without the four framework-service plugin
repos being checked out as siblings, and their composer-ci.json refs
(dev-claude/trusting-ptolemy-TFTfV) no longer matched the ci.yml `ref:
main` checkouts — so the path-repo resolution was already broken. But
nothing under app/ or tests/ references a plugin class: phpstan analyzes
app/ only, and the unit tests never boot the framework router or
instantiate a plugin, so the plugins are dead weight for this repo's CI.

- composer-ci.json: drop the four gcgov/framework-service-* requires and
  their path repositories; keep only the gcgov/framework path repo. Now
  `cp composer-ci.json composer.json && composer install && composer ci`
  resolves against just the sibling framework checkout — runnable in the
  Claude Code sandbox and any environment with only the framework present.
  The committed composer.json still lists the plugins for real scaffolds.
- ci.yml: remove the four plugin sibling checkouts from both jobs.
- README: document running the CI checks locally.

Verified in-sandbox: phpstan clean, 42 tests pass, using only ../framework.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWacdhEDN9upUvYeagNKru
Resolves gcgov/framework through a vcs repository against its v7 branch so
the Docker and CI work can proceed before v7.0.0-rc.1 is tagged. This is
temporary: at RC the constraint becomes ^7.0, the repositories entry goes,
and the lock is regenerated.

- composer.lock committed, pinned via config.platform.php to 8.4.0 so the
  lock always resolves for the runtime the prod image runs. Without the pin,
  resolving on a newer PHP locks eight Symfony packages requiring >=8.4.1
  that would not install in the image.
- php >= 8.4; Dockerfile base moves to php:8.4-fpm.
- CI installs from the committed lock. Removes the sibling-checkout step
  (actions/checkout rejects a path outside the workspace, so this job had
  never run green), the stale framework branch ref, and composer-ci.json
  along with the swap-and-delete-the-lock dance it required.

Verified: lock resolves 110 packages with nothing requiring >8.4.0, and
`composer install --no-dev` installs cleanly with every framework and app
class autoloading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6
…lease caller

config.json
- Down to five required references: APP_TYPE, APP_ROOT_URL, APP_BASE_PATH, the two
  redirect urls, MONGO_DATABASE and %env(secret:MONGO_URI)%. The microsoft,
  payjunction and SMTP blocks are gone — pre-wiring payment processing into every
  county application meant every one had to be handed credentials it does not use in
  order to boot. An absent section hydrates to its defaults.
- No {token} syntax anywhere: tokenReplacer no longer exists, so the tokens left in
  app.php would never have been replaced. OpenAPI metadata is now literals with a
  relative server url, which is correct in every Environment.
- .env.example keeps only the compose variables; the application's come from
  `gf env --init`, so the two files cannot drift into each other.

PHP runtime
- docker/php/conf.d/app.ini is what the four deleted php.ini files were never replaced
  with: the base image ships none, so opcache was off and every limit was a compile
  default. Fixes the live mismatch where nginx accepted a 1024m body that PHP then
  rejected. JIT stays off — an API bound on Mongo I/O gains nothing from it.
- The pool config replaces www.conf rather than layering on it: www.conf's user/group
  need a root master, and running one on a public-facing host to gain privilege
  separation inside an already single-purpose container is a bad trade.

Images
- Separate php and nginx targets from one context, so the pair cannot serve mismatched
  releases. nginx gets only www/, never the application's PHP.
- APP_VERSION build arg surfaces through /health, so a deploy can be verified.
- HEALTHCHECK exercises nginx → FPM → PHP → the route table rather than asking whether
  a process is running. Liveness only, via a configurable HEALTH_URL; readiness is the
  deploy gate's business.

CI additionally asserts what the image must never contain (.env, JWT keys), that it
runs as www-data without the FPM root warning, and that post_max_size still matches
nginx's limit.

47 tests pass; PHPStan clean. The image build itself is unverified — no Docker daemon
in this environment — so CI is its first real exercise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6
The caller named build-and-deploy.yml, which gcgov/deploy does not have, and its comment
claimed the deploy step ran on a Zone's self-hosted runner through this call. It does
not, and it must not: a called workflow executes in the caller's context, so deploying
that way would give every contributor to an application repository code execution inside
a network Zone.

The ops repo splits it in two — build.yml is callable and GitHub-hosted; deployment
arrives separately by repository_dispatch and runs in the ops repo's own context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6
Moves the application off the framework's development branch onto a published release:
rewrites the constraint, deletes the temporary vcs bridge, regenerates the lock, and
verifies it installs and the suite passes.

The bridge exists only because there was no published v7 to depend on. Once one is
tagged it becomes the kind of indirection that quietly stays for years, so removing it
is part of adopting a release rather than a separate cleanup nobody schedules.

Run with '^7.0@RC' at the release candidate and '^7.0' at v7.0.0. config.platform.php
stays either way — it pins resolution to the PHP the production image runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6
Replaces the temporary vcs bridge: the constraint is now ^7.0@RC and the lock pins the
published release rather than a commit on a development branch. The `repositories` entry
is gone. At v7.0.0 the constraint becomes plain ^7.0 —
`scripts/adopt-framework-release.sh '^7.0'` does it.

Two fixes to that script, both found by running it:

- `composer validate` ran before the lock was regenerated, so it failed on a staleness it
  had just deliberately caused. It now validates the file alone first and the lock after.
- Documented that blanket --ignore-platform-reqs must never be used here. It discards
  config.platform.php along with the extension checks, which locked symfony/filesystem
  v8.1.5 (php >=8.4.1) against a pin of 8.4.0 — the subsequent `composer install`
  refused it, which is exactly what the pin is for.

Verified: framework v7.0.0-rc.1 installs from Packagist, every framework and app class
autoloads including the new health router, 47 tests pass, PHPStan clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kqJazGHDMq19pQ64732i6
The framework no longer has \app\app::registerFrameworkServiceNamespaces().
The five services live inside it and are switched on by a `services` section
of config.json, so enabling a service and configuring it are one statement.

app.php loses the method and keeps only its lifecycle hooks. The class is
still required: config derives every path in the framework by reflecting on
its file location.

router.php implements the new interfaces\appRouter and answers its
providesAuthentication() with false. That is the load-bearing line here.
config.json enables services.auth, whose guard establishes identity;
authentication() below it returns true for everyone, so claiming to provide
authentication would satisfy the framework's boot check while leaving all
four widget routes open to anyone. Verified both ways: with services.auth
present a request reaches the guard and is refused for a missing
Authorization header, and with it removed the application refuses to boot,
naming the four routes.

The comment block describing how auth wires up was rewritten. It described
authoauth "automatically adding our guard" and a
getRunFrameworkServiceRouteAuthentication() that was never implemented here
and is no longer duck-typed — the per-route opt-out is now implementing
interfaces\router\skipsServiceAuthentication.

constants.php held DEFAULT_ROLES, which existed only to be passed to the
commented-out setBlockNewUsers() calls in app.php. That configuration is now
services.auth.defaultNewUserRoles, so the constant had no consumer left. It
is replaced by the widget role constants the route table was carrying as
string literals, which is the convention the framework documents, and a test
asserts every role a route requires is one of them.

Two things translate to nothing rather than to something:

- cronMonitor was registered but never configured. appDictionary is empty, so
  the service was constructed with an empty base URI and every ping went
  nowhere. An empty cronMonitor.url block would be the present-and-blank
  optional integration ADR 0001 rejects, so it is simply gone.
- settings held only useSession, which the framework removed. An empty
  settings block is the same problem; a missing section hydrates to defaults.

Both are now asserted absent alongside microsoft and payjunction.

composer.json drops the four service requires. composer.lock is deliberately
NOT regenerated: there is no framework release containing these changes to
regenerate against, and regenerating against v7.0.0-rc.1 would install
cleanly and produce a template whose authenticated routes have no guard and
no boot check to catch it. Loudly unbuildable beats silently unprotected.

So CI is red on this branch until a release is tagged, at which point
scripts/adopt-framework-release.sh clears it. To make that state legible, CI
now runs composer validate before composer install: install treats a lock
that disagrees with composer.json as a warning and carries on from the stale
lock, which would install the wrong framework and fail later as
class-not-found errors. validate exits non-zero naming the actual problem.

50 tests pass against the framework branch. One PHPStan finding predates this
change: authentication() carries a @throws its body does not use, identical
on v7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmBBiV3fQaaRdrmspZarS5
config.json hard-coded "keyPath": "" with no %env() reference, so the framework
fell back to {root}/srv/jwtCertificates — a directory no built image contains,
because the keys are secrets and are gitignored. A container therefore mounted
its provisioned keys and then looked somewhere else for them.

Nothing catches this at start-up: /api/health and /api/health/ready never
construct the JWT service, so the container reports healthy, the deploy health
gate passes and the Release is recorded as good. The first symptom is a
configException naming a key directory the moment a user tries to sign in.

keyPath now reads %env(APP_JWT_KEY_PATH)%, which the value genuinely varies by
environment: srv/jwtCertificates locally, /run/secrets/<app>/jwt in a Zone.
`gf env --list` picks it up from config.json, so the .env manifest cannot drift
from it. DOCKER.md gives the value for both cases and explains why the failure
looks like success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NP3QWawCQVvvzFLQMR5iZ
One .env cannot hold a JWT key path that is right on both filesystems: the
host gf CLI (cert:generate-auth) writes the keys on the host, while the
container reads them at /var/www/app. DOCKER.md used to document the
container path as the .env value, which broke whichever consumer read it
second. The compose file now sets the container value itself (environment
wins over env_file), and .env carries a root-relative host path for the CLI.

DOCKER.md's key-mount section also now describes the framework's readiness
check: with services.auth enabled, /api/health/ready fails when the key
directory holds no usable keys, so an unmounted key directory stops a deploy
at the health gate instead of surfacing at the first production sign-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012SmBhj1hdgf4hFcvCN78m5
Following this repository's own instructions did not produce a working
application. Four blockers, each fixed here.

- MongoDB ran as a standalone, so every write failed. The framework opens a
  transaction for every save and delete it is not handed a session for, and
  MongoDB offers transactions only on a replica set or a sharded cluster. The
  compose stack now runs a single-member set and initiates it from the mongodb
  healthcheck, with php waiting on service_healthy rather than service_started —
  the healthcheck is what initiates the set, so waiting for the process is
  waiting for a node that cannot accept writes. Reads worked throughout, which
  is why this survived a smoke test: /widgets listed, and saving one did not.

- MONGO_URI now joins APP_JWT_KEY_PATH in the compose file's environment block.
  .env is shared by the host gf CLI and the container, and one variable cannot
  name both localhost and the compose service. The host's value needs
  directConnection=true, because the set advertises a member name only the
  compose network resolves.

- One .env, not .env + .env.local. Compose interpolates ${HTTP_PORT} and the
  CORS origins from .env and never reads .env.local, so the documented
  `cp .env.example .env.local` left every one of them silently on its default —
  and .env.example's own header disagreed with both READMEs anyway. Both env_file
  entries are now required:false, so `docker compose run` works before .env
  exists, which is when the bootstrap needs it.

- No way to create the first user. Now `gf user:create`, which the framework
  gained for this.

LOCAL-DEVELOPMENT.md is the walkthrough: prerequisites through a signed-in
request that writes a document, the two variables that differ by side, and a
troubleshooting table keyed on the message you actually see. Bootstrap is
container-first, so Docker and git are the whole prerequisite list and a host
PHP toolchain is optional.

ConfigFilesTest gains guards for the three invariants that broke silently: the
replica-set flag, the pinned container variables, and .env.example declaring
every variable compose interpolates. Its DEV_ENVIRONMENT was also missing
APP_JWT_KEY_PATH, added to config.json earlier without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PQDyvhd1iz7g1AF6RsCdT
Follow-up from a design review of the local development work.

- LOCAL-DEVELOPMENT.md keeps only what is genuinely this stack's: the runnable
  sequence, the concrete values (mongodb:27017, rs0, HTTP_PORT, /widgets,
  Widget.Read), and the troubleshooting table. Every "why" now links to the
  framework's readme/local-development.md. The seam falls where the drift danger
  is: this repository is a GitHub template, so an application's copy of this file
  is frozen the moment it is scaffolded, while the rules behind it reach that
  application through Composer and stay current.

- A suspected Linux-only bootstrap failure is documented rather than fixed. The
  dev image ends USER www-data (uid 33), so `docker compose run --rm php
  vendor/bin/gf init` writes into a bind mount as uid 33 and should fail on
  config.json, which the host owns. Docker Desktop's file-sharing layer masks
  this on macOS and Windows, which is why it went unnoticed. It has NOT been
  reproduced — there was no Docker daemon available — so the row is worded as
  suspected and carries the --user workaround. A developer who hits it gets a
  path forward; if the diagnosis is wrong the row simply never matches.

- symfony/yaml moves from a transitive dependency to a declared one.
  ConfigFilesTest parses docker-compose.yml with it, which worked only because
  the framework happens to pull it in — the day that stops being true the test
  breaks for a reason nothing points at. composer.lock is refreshed for the new
  content hash only; no resolved version changed, and `composer validate`, which
  CI runs first, passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PQDyvhd1iz7g1AF6RsCdT
A developer working on an application with years of documents in it wants a copy
of real data, and until now this stack offered no way to load one. `gf db:restore`
was the v6 answer and was removed in v7, because it put production credentials on
every workstation.

A dump file is the replacement, and this is the half that runs it:

  mongodump --uri="<source uri>" --db=myapp --out=db/backup
  docker compose run --rm mongo-restore

`mongo-restore` is a one-shot container from the same mongo:7 image as the
database, held out of `docker compose up` by a profile, so a restore never
happens as a side effect of a boot. It restores db/backup/{DatabaseName} into
MONGO_DATABASE with --drop, so it replaces every collection the backup holds and
leaves anything else alone.

It builds its connection from the compose service name instead of reading
MONGO_URI. The only database a restore can write to is therefore the one beside
it, whatever .env happens to hold — which is the property that made db:restore
worth removing in the first place.

db/backup/ is git-ignored and excluded from the build context: the php stage
does `COPY . /var/www/app`, so a dump left there would otherwise ship inside a
Release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rw19sb82Jy2GQTLdd7N8f7
The v7 review moved the operational ADRs into gcgov/deploy and renumbered
them. Point DOCKER.md at the moved secrets ADR (gcgov/deploy 0001), replace
the stale "GCP KMS key per Zone" wording with Azure Key Vault (one vault per
Zone, gcgov/deploy 0004), and update the replica-set ADR link in
LOCAL-DEVELOPMENT.md to framework's renumbered 0004.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzByhoDp7hsD39aoThJ9rv
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.

2 participants