mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
main
14 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cf33342500 |
feat(prole): bootstrap knoe-auth on k3s; tenant onboarding; cluster stabilisation
knoe-auth (prole.org k3s): - Fix CNPG manifest drift: remove spec.backup.pluginConfiguration (CNPG 1.28 only), switch spec.certificates from serverTLSSecret to serverAltDNSNames - Apply knoe-auth Round 1 schema + GRANTs manually (postInitSQL had never run on live cluster) - Fix OIDC signing key generator: base64(DER) not base64(PEM) — OidcTokenService does Base64.decode() → PKCS8EncodedKeySpec which requires raw DER bytes - Add OIDC controllers: authorize, token, userinfo, jwks, discovery - Add prole Spring profile: cookieDomain, emailDomain, Kerberos config - Add secret example templates: knoe-db-user, knoe-auth-oidc-signing, knoe-auth-google-prole - Kong configmap: scope knoe-auth route to /auth prefix only Tenant onboarding: - Add etc/onboard_tenant.sh: provision/apply/rotate/status workflow backed by 1Password vaults; types: 'enterprise' (own Kerberos + domain) and 'tenant' (hosted, initContainer KDC) - Provision 'Knoe Tenant - prole.org' vault; apply all 7 k8s secrets to knoe-system - init_knoe_auth.sh: add explicit GRANT + ALTER DEFAULT PRIVILEGES for knoe role Cluster stabilisation: - gitea: roll back 14-day stuck rollout (RWO PVC + maxSurge=100% deadlock); patch deployment strategy to Recreate - supabase: create supabase_admin role, _supabase db, _analytics schema, _realtime schema in CNPG — analytics and realtime had never connected since Helm install day 1 - knoe-db barman ObjectStore: add GCS-backed objectstore manifest + scheduled backup Infrastructure: - gandalf host_vars: k3s registry config - pi host_vars: clean up stale entries - knoe-db schemas: ekosystem.sql, ekosystem_objects.sql - init_prole_app.sql: prole app DB initialisation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
6f506a97b2 |
docs(plans): file todo-1 brief — cfg save path refuses non-string widget values
Followup from commit dba8a2d's findings. ConfigMixin._save_knoe_cfg in
knoe/ui/screens/cfg.py reads Tk widget vars via `var.get()` and writes
the result to conf/<mode>.cfg. When a var is a MagicMock (interactive
./install.py run in a non-Tk context, partially-mocked widgets), the
save path serializes the mock's repr-string into the cfg, then the next
installer pass calls os.makedirs() on those values and produces
directories literally named `<MagicMock name='Canvas().tk.call().strip()'
id='4743999712'>/`.
The brief lays out a TDD approach for Junie:
1. Write failing test at tests/installer/test_cfg_save_refuses_mock_values.py
that passes MagicMock widget vars and asserts _save_knoe_cfg raises
TypeError naming the field.
2. Implement the minimal fix: a `_str_value(var, field=...)` helper in
cfg.py that validates widget reads and raises if non-str. Use it
in the .get()/.strip() chains across lines 103-155.
3. Verify the 750 existing installer tests still pass.
Brief includes file pointers (cfg.py:44 _save_knoe_cfg, line 102
globals_to_save assembly, line 277 cfg_path.write_text), the canonical
failing test stub, both fix-approach options (per-read validator vs
end-of-flow dict walk), and explicit commit-shape guidance.
Index updates:
docs/plans/junie/README.md — todo-1 row added under Active
docs/TODO.md §"In progress" — todo-1 promoted above Phase 3 (smaller
scope, easy to land first)
Naming convention: `todo-N-<slug>.md` for follow-up bugs, distinct from
the `NN-<slug>.md` pattern reserved for ranked queue items.
|
||
|
|
2b36add592 |
docs(plans): file Phase 3 brief — knoe-auth as a pod inside k3d
Phase 1 (host loop) and the Phase 2 OIDC sandbox are shipped; this
brief queues Phase 3 of k3d-mirror-of-GKE: build the knoe-auth image,
k3d-import, run as a pod inside the cluster. Pre-merge smoke loop —
not the daily inner loop.
Deliverables (see brief for the full Definition of done):
- k8s/knoe/knoe-auth-deployment.yaml (NEW; sibling of GKE manifest)
- scripts/k3d-knoe-deploy.sh (NEW; build + import + apply + wait)
- Makefile k3d-knoe-{deploy,redeploy,undeploy} targets
- Phase 2 signing key flows from etc/secrets/knoe-auth-oidc-key.b64 into
a knoe-auth-oidc-signing-key K8s Secret in knoe-system, mirroring how
the GKE deploy reads it.
Index updates:
docs/plans/junie/README.md — k3d Phase 3 in Active row
docs/TODO.md §"In progress" — promotes the brief to top
docs/plans/k3d-gke-mirror.md — banner + §6 Phase 3 entry
flipped from "out of scope"
to "in flight"
|
||
|
|
903f84f200 |
feat: ship Junie #3 (image rename) + Phase 2 OIDC GKE deploy + k3d chrisfu seed
Three independent lines of work landing in one commit because they were all on disk together end-of-session and the cross-file edits (TODO, knoe-system, junie/README) interleave cleanly: 1. **Queue #3 — image rename `knoe-authority` → `knoe-auth`** (Junie). Closes drift R6. The Maven artifact has been `knoe-auth.jar` since commit b355855; the deploy manifests now match. authority/Dockerfile.app (NEW) deploy/gcp/gke/knoe-auth-deployment.yaml (3 image tags renamed) deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml (2 image tags renamed) Makefile (docker-build-auth + docker-push-auth + REGISTRY/KNOE_AUTH_VERSION defaults) docs/plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md (brief, kept as design record) 2. **Phase 2 OIDC provider — GKE deploy** (Junie). Source landed via the merge that brought claude/crazy-bose-fec256 back; the k3d sandbox shipped earlier today (commit |
||
|
|
93157b0a86 |
feat(knoe-auth): Phase 2 OIDC sandbox in k3d dev loop
Completes the Phase 2 OIDC laptop dev path. Source for the OIDC surface
(discovery, authorize, token, userinfo, JWKS controllers + signing /
session services) landed on `main` via the merge that brought
claude/crazy-bose-fec256 back. This commit makes Phase 2 actually
exercisable in the k3d dev loop without any GKE deploy.
What's new:
authority/src/main/resources/application-k3d.yml
Spring profile activated by `-Dspring-boot.run.profiles=k3d`.
Enables OIDC (knoe.oidc.enabled=true), points the issuer at
`http://localhost:8080`, sets Kerberos realm to KNOE.LOCAL, and
aligns the datasource with the port-forwarded localhost:5432 DB.
etc/gen_oidc_signing_key.sh (executable)
Idempotent RS256 PKCS#8 keypair generator. Outputs:
etc/secrets/knoe-auth-oidc-key.pem (PEM)
etc/secrets/knoe-auth-oidc-key.b64 (single-line base64 of DER —
directly consumable as KNOE_AUTH_OIDC_SIGNING_KEY by
OidcTokenService.init())
`etc/secrets/` is already gitignored. Set FORCE=1 to rotate.
What's wired:
scripts/k3d-knoe-up.sh
New §7 calls etc/gen_oidc_signing_key.sh after schema seed. Keypair
persists across `make k3d-knoe-down && make k3d-knoe-up` cycles.
scripts/k3d-knoe-pf.sh
Output now includes the KNOE_AUTH_OIDC_SIGNING_KEY export line, the
full `mvn spring-boot:run` invocation with -Dspring-boot.run.profiles=k3d,
and the three OIDC endpoints to curl-test.
What's documented:
docs/local-dev-knoe-auth.md
"Daily loop" Terminal B: now exports KNOE_AUTH_OIDC_SIGNING_KEY,
runs with `-Dspring-boot.run.profiles=k3d`, and the verify section
includes /jwks.json. IntelliJ run config: adds Active Profiles: k3d
and a note about pasting the b64 directly (no shell expansion in
the env-var field).
docs/knoe-system.md
Phase 2 status row split: "k3d setup" → Shipped, "GKE deploy" →
Pending. The "Open work items" Phase 2 entry rewritten to flag
that the GKE deploy is the remaining thread (gated on queue #3 for
the image rebuild as `knoe-auth:latest`).
docs/TODO.md
Promoted "Phase 2 OIDC provider — GKE deploy" into §In progress
(replacing the empty "(none)" placeholder). Done section updated
with two entries: the k3d Phase 1 dev loop (Junie's c3...) and
this Phase 2 OIDC k3d sandbox.
End-to-end loop the engineer can run:
make k3d-knoe-up # one-time, ~5 min
make k3d-knoe-pf & # port-forwards
export KRB5_CONFIG=$PWD/etc/krb5.local.conf
export KNOE_AUTH_OIDC_SIGNING_KEY=$(cat etc/secrets/knoe-auth-oidc-key.b64)
mvn -pl authority spring-boot:run \
-Dspring-boot.run.jvmArguments="-Djava.security.krb5.conf=$PWD/etc/krb5.local.conf" \
-Dspring-boot.run.profiles=k3d
# then:
curl -s http://localhost:8080/.well-known/openid-configuration | jq .issuer
# → "http://localhost:8080"
curl -s http://localhost:8080/jwks.json | jq '.keys[0].kty'
# → "RSA"
Verified locally: keypair generator round-trips through openssl pkey -inform DER
(produces valid 2048-bit RSA keys); idempotent (existing key kept by default,
FORCE=1 rotates); bash -n clean on all 5 touched scripts.
Out of scope (TODO §In progress captures it):
- GKE deploy of Phase 2 (image rebuild + K8s Secret + deployment env vars)
- SPNEGO E2E from host browsers (k3d-mirror Phase 2)
- knoe-auth-as-pod in k3d (k3d-mirror Phase 3)
- OidcCodeService DB persistence (separate track)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5d3600845d |
feat(k3d): laptop dev loop for knoe-auth — CNPG + KDC + port-forward
Brings up the smallest k3d-resident stack that lets a host-side knoe-auth
(run via `mvn spring-boot:run` or IntelliJ) iterate against real Postgres
+ Kerberos. Closes Phase 1 of the k3d-gke-mirror plan (docs/plans/k3d-gke-mirror.md).
Scope:
- k8s/knoe/knoe-kdc-{configmap,deployment,service,pvc,init-job}.yaml
NEW; standalone KDC, realm KNOE.LOCAL (distinct from KNOE.DEV).
- etc/init_knoe_auth.sh: --mode k3d flag added; swaps realm + skips
GCP-specific steps. GKE behavior unchanged when flag absent.
- Makefile: k3d-knoe-up, k3d-knoe-pf, k3d-knoe-down (delegate to
scripts/k3d-knoe-{up,pf,down}.sh).
- scripts/k3d-knoe-{up,pf,down,smoke}.sh NEW; up = full bring-up,
pf = three port-forwards (5432/88/464) + JDBC URL + ^C cleanup,
down = teardown, smoke = sanity check.
- etc/krb5.local.conf NEW; checked-in libdefaults+realms config
pointing at localhost:88. udp_preference_limit=1 to dodge
kubectl port-forward UDP flakiness on macOS.
- docs/local-dev-knoe-auth.md NEW; one-time setup + daily loop +
IntelliJ run config.
- docs/knoe-system.md NEW; unified reference for the knoe-auth
service (GKE deployment + k3d dev loop + schema overview +
source map + open work items).
Verified per the brief's Definition of done: fresh-clone laptop can
`make k3d-knoe-up` + `make k3d-knoe-pf` + `mvn -pl authority spring-boot:run`
and hit /health, /.well-known/openid-configuration in <8 minutes.
Out of scope (parent plan docs/plans/k3d-gke-mirror.md §6):
- SPNEGO from host browsers (Phase 2)
- knoe-auth-as-pod / image build/load (Phase 3)
- Supabase stack on k3d (Phase 4)
- OidcCodeService DB persistence (separate track)
docs/plans/junie/README.md — k3d brief moved from Active to Shipped.
docs/TODO.md — In-progress now empty; Phase 2 pg_oauth notes that the
local dev loop is in place so it can resume.
Closes Phase 1; Phase 2+ briefs filed as needed.
Co-authored-by: Junie <junie@jetbrains.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6d484ef13e |
docs(plans): k3d-mirror-of-GKE plan + Phase 1 brief for Junie
The next development thread is knoe-auth Phase 2 OIDC iteration, which
just landed on main but has no fast inner-loop. Instead of bouncing
edits through the GKE deploy chain, we want a laptop-resident model
where knoe-auth runs from the IDE / mvn against real Postgres + KDC
brought up in k3d.
Two new docs:
docs/plans/k3d-gke-mirror.md
Architectural plan. Top-to-bottom rationale for what the k3d
model should mirror, what we deliberately skip (supabase, kong,
oauth2-proxy, Studio — all unnecessary for knoe-auth's surfaces),
and the phasing. Captures the three scope decisions taken
2026-05-02 with the user:
- knoe-auth runs on the laptop, not as a pod (faster inner loop)
- stack scope is CNPG + KDC only
- single-replica CNPG, realm KNOE.LOCAL (distinct from KNOE.DEV)
Out-of-scope for Phase 1 listed explicitly so reviewer
expectations match scope: SPNEGO E2E, image-build-and-load,
supabase, oauth2-proxy, OidcCodeService DB persistence.
docs/plans/junie/k3d-knoe-auth-dev-loop.md
Phase 1 brief. Concrete deliverables:
- k8s/knoe/knoe-kdc-{deployment,service,configmap,pvc,init-job}.yaml
- etc/init_knoe_auth.sh --mode k3d flag (or sibling script)
- Makefile: k3d-knoe-{up,pf,down}
- etc/krb5.local.conf (with udp_preference_limit=1 for
kubectl port-forward UDP flakiness on macOS)
- docs/local-dev-knoe-auth.md (engineer-facing)
- scripts/k3d-knoe-smoke.sh
Definition of done: a fresh-clone laptop can `make k3d-knoe-up`
+ `make k3d-knoe-pf` + `mvn -pl authority spring-boot:run` and
hit /health, /.well-known/openid-configuration in <8 minutes.
Index updates:
docs/plans/README.md — k3d-gke-mirror.md row added
docs/plans/junie/README.md — split into Active / Shipped;
k3d brief listed under Active;
the 5 shipped-2026-05-02 briefs
moved to Shipped with commit refs.
docs/TODO.md In-progress — k3d brief now top of "In progress";
the previously in-flight "Phase 2
pg_oauth in install/deploy.sh"
item demoted to "paused" (resume
after the local dev loop lands).
No code changes; all docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
232981c18c |
chore(cleanup): decommission garage on knoe-dev-0 app cluster (queue #4)
Mirroring the 2026-04-29 DB-cluster removal. Live state showed garage in an unconfigured limbo (layout v0, no role assigned, 21d of "Ring not yet ready" warnings); nothing outside garage's own state referenced its S3 endpoint anymore. Removed from knoe-system namespace on knoe-dev-0: - statefulset/garage (1/1 pod, 21d uptime) - service/garage (ClusterIP; no garage-s3-ilb on app cluster) - configmap/garage-config - secret/garage-secrets - pvc/data-garage-0 (29 Gi, garage-hdd) - pv/pvc-656e1936-... (was Bound, reclaim=Retain) - storageclass/garage-hdd Underlying GCE pd-standard disk (us-west3-c/pvc-656e1936-...) reclaimed via `gcloud compute disks delete` since reclaim=Retain leaves the disk behind after the PV is gone. Source manifests intact: - k8s/knoe/garage-*.yaml (k3d, min) - deploy/opentofu/k3s/manifests/knoe/garage-*.yaml (k3s) - etc/init_garage_store.sh + knoe/core/ops/k8s_garage_store.py - k8s/knoe/garage-statefulset-gcp.yaml (GKE variant) A future ./install.sh against GKE could re-deploy garage if that's what the deploy mode wires; whether it SHOULD wire garage given GCS now handles backups is a separate cleanup decision. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e52bbb8982 |
chore(cleanup): drop 5 stale resources; reframe queue #4 (garage)
Queue #5 Phase B orphan cleanup landed: App cluster (knoe-dev-0): - gitlab/gitlab-migrations-58a3e27-97-23-n72j8 (Failed/Evicted, 11d stale) - pv/pvc-42e8545b-… (10Gi, monitoring/storage-kps-grafana-0) - pv/pvc-86422179-… (30Gi, monitoring/prometheus-…-prometheus-0) - pv/pvc-e2c29f7a-… (5Gi, monitoring/alertmanager-…-alertmanager-0) DB cluster (knoe-dev-cnpg-0): - kube-system/prole-supabase-dirprep Job (FailureTarget, 22d stale) Post-state: 0 Released PVs on app cluster; gitlab namespace has only the Completed migration pod; DB-cluster kube-system has no prole-* artifacts. Live-cluster ops only; no source changes. Queue #4 reframed: tried the planned `garage repair --yes blocks`, but found garage in an unconfigured state — layout v0, no role assigned, "Ring not yet ready" warnings continuous since whenever the layout was lost (likely during a reconcile after the 2026-04-29 PVC resize). Cross-cluster sweep shows nothing outside garage references its S3 endpoint anymore (CNPG backups moved to GCS, no other workload points at 10.180.15.239:3900). Garage on app-cluster is now a dead pod holding an 80 GiB PVC. The original block-repair task is moot. Reframed as a decommission-or-restore decision; details captured in the ranked queue entry. Awaiting direction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2ca2b8b234 |
docs(todo): note PodMonitor + DASHBOARD applied to live; node-label partial closure
Adds a 2026-05-02 Done entry covering the cluster-side work that paired with |
||
|
|
c3fae73de3 |
fix(cnpg,kong): wire cnpg-backup-sa, migrate PodMonitor, drop DASHBOARD consumer
Three Junie briefs landed in one commit: #07 — Wire cnpg-backup-sa into CNPG cluster spec (drift R8) deploy/gcp/gke/knoe-db.yaml: add spec.serviceAccountName: cnpg-backup-sa (requires CNPG v1.29+, which is the live operator version). etc/init_cnpg_gke.sh: operator install URL now uses CNPG_OPERATOR_VERSION variable (default 1.29.0); new §11 patches knoe-db and knoe-db-barman-cloud RoleBindings to add cnpg-backup-sa as a subject if not already present — matching the 2026-04-29 live stabilization. #13 — Migrate off deprecated enablePodMonitor + podMonitorRelabelings Both deprecated fields removed from deploy/gcp/gke/knoe-db.yaml spec.monitoring. New deploy/gcp/gke/knoe-db-podmonitor.yaml carries the PodMonitor with the cluster relabeling rule (cnpg.io/cluster pod label → cluster label; required for all 85 CNPG Grafana panels). Apply alongside knoe-db.yaml on next cluster patch. #15 — Remove dead DASHBOARD consumer + basicauth_credentials supabase/helm/knoe-supabase: - wrapper.sh: drop DASHBOARD_USERNAME / DASHBOARD_PASSWORD envsubst lines - config.yaml: drop DASHBOARD consumer + basicauth_credentials block - kong/deployment.yaml: drop both DASHBOARD env-var secret refs - values.yaml: rename secret.dashboard → secret.openai (apiKey only; username/password dropped — no enforcer since commit 25f1b2e) - secrets/dashboard.yaml + _helpers.tpl: renamed to openai / supabase.secret.openai - studio/deployment.yaml: reads from secret.openai.apiKey - ci/example.yaml: updated to secret.openai.apiKey helm template confirms knoe-supabase-openai secret referenced; no DASHBOARD output. docs/TODO.md: queue items #7, #13, #15 + drift row R8 archived to Done. Co-authored-by: Junie <junie@jetbrains.com> |
||
|
|
5d17325c10 |
fix(scripts): patch_garage_cross_cluster — three defects from 2026-04-29 review
Defects A, B, C from docs/plans/junie/06-patch-garage-script-fixes.md:
A. DB_CLUSTER default was knoe-cnpg-0 (stale rebrand artifact);
corrected to knoe-dev-cnpg-0 to match conf/gke.cfg and the
cluster table in CLAUDE.md. Header comment + overrides block
also updated.
B. Phase 1 deletion loop missed service/garage-s3-ilb (the
LoadBalancer the ObjectStore endpoint historically pointed at).
Now included alongside service/garage.
C. Phase 2 was applying knoe-db-backup-gcs.yaml whole, including a
legacy ScheduledBackup using method:barmanObjectStore (being
removed in CNPG v1.30). Manifest split handled in prior commit
(
|
||
|
|
500c9b1317 |
fix(installer): env-contamination guard against shell-context / config mismatch
Filed in response to the 2026-04-28 14:00 UTC backup outage. An
`install.sh --mode k3d` run with the shell pointed at GKE silently
overwrote the GKE cluster's GCS-backed ObjectStore + ScheduledBackup
with k3d-mode defaults; Garage filled up and CNPG backups failed for
hours before the next manual check. The class of bug is "config says
target cluster A, shell context says target cluster B, installer
proceeds against B without warning."
New shared bash helper at etc/preflight_kubecontext.sh with two
functions:
- verify_kubecontext_matches_config <cfg-path>
Strict gate. Reads [Global] APP_CLUSTER_KUBECONTEXT from the
config and exits 1 if `kubectl config current-context` differs.
Skipped silently when the config has no baked APP_CLUSTER_KUBECONTEXT
(e.g. fresh k3d.cfg) or when there's no live current-context.
- print_kubecontext_notice
Informational. Prints what's about to be inherited so the user
can abort before the TUI launches if it looks wrong. Never fails.
Wiring:
- deploy.sh sources the helper and calls the strict gate against
${PROLE_DEPLOY_CFG:-conf/gke.cfg} before invoking Python.
Unattended path -> hard refusal on mismatch.
- install.sh sources the helper and calls the informational notice
(gated on not-`--min`) right after entering the local-checkout
branch. The TUI is interactive, so the strict mode-aware gate is
a follow-up once the welcome screen records a mode in
state.inputs.
Bypass for deliberate cross-cluster maintenance:
KNOE_SKIP_KUBECONTEXT_GUARD=true ./deploy.sh
End-to-end verified:
- deploy.sh with current=cnpg-0, gke.cfg=app-0 -> exit 1, clear msg
- deploy.sh with KNOE_SKIP_...=true -> bypasses, prints
"skipping check"
- install.sh --min -> notice skipped
- install.sh (no flag) and install.sh --silent -> notice printed
Doc updates:
- CLAUDE.md §"Env-contamination warning" rewritten to describe the
live guard (was a forward-looking TODO).
- CLAUDE.md drift table row R4 removed; "Closed 2026-05-01" line added.
- docs/TODO.md queue item #1 archived to Done; R4 dropped from the
reality-vs-intent table. Queue numbering retained (no #1 placeholder)
so the docs/plans/junie/<NN>-...md filenames still match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f83ec8169c |
docs(plans): file Junie briefs for queue items #2, #6, #7, #13, #15
Five self-contained work briefs in a new docs/plans/junie/ subdirectory, each tagged against a numbered item in docs/TODO.md so Junie can take them independently in any order. 02-k3s-prole-rename.md -> queue #2 (drift R5) 06-patch-garage-script-fixes.md -> queue #6 (drift R9) 07-init-cnpg-gke-sa-wiring.md -> queue #7 (drift R8) 13-podmonitor-manual-management.md-> queue #13 15-remove-dead-dashboard-consumer.md-> queue #15 Each brief follows the same shape: Why -> What changes (concrete file paths + line numbers + before/after) -> Verification -> Out of scope -> Commit shape -> Definition of done. The intent is that Junie reads cold (no shared chat history) and lands the change without escalating questions. Also adds: - docs/plans/junie/README.md describing the convention. - Row in docs/plans/README.md so newcomers find the subdirectory. - Brief reference + "Assigned to Junie" tag on each of the five queue items in docs/TODO.md. Existing in-progress assignment to Junie (Phase 2 pg_oauth) is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |