Skip to content
Merged
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
2 changes: 1 addition & 1 deletion AINDY/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.26.0"
__version__ = "2.27.0"
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,76 @@

_Nothing yet._

## 2.27.0 — 2026-10-09

**Operator notes — read before upgrading.** Handoff: `docs/upgrades/APP_HANDOFF_v2.27.0.md`.
No ui-kit release; `@aindy/ui-kit` 2.1.1 stays current.

- **No migration.** Alembic head stays `0020` and the schema contract stays `2026-09-20`. Nothing
under `AINDY/db/models/` changed, so the `Upgrade Path Guard` passes trivially on this release,
and its negative control is the half that carries meaning.
- **python-jose, `ecdsa`, `rsa` and `pyasn1` are no longer installed** (replaced by
`PyJWT[crypto]`; CVE-2026-85394). A plugin that imports any of them undeclared breaks. Issued
tokens are byte-identical and keep verifying.
- **Every request with plugins loaded was ~3 s slower since 2.25.0** (FR-52); fixed.
- No route started enforcing a new scope. No default changed.

### Changed — the JWT library is PyJWT; python-jose, ecdsa, rsa and pyasn1 leave the install (#810)

**Operators: read before upgrading** if any plugin or extension in your deployment imports
`jose`, `ecdsa`, `rsa` or `pyasn1` without declaring it — the runtime no longer installs them.

- **Why.** `CVE-2026-85394` / `GHSA-3qf3-8w2g-rqmx` (critical, python-jose <= 3.5.0, no fix
released): the algorithm-confusion guard accepts a DER-encoded public key as an HMAC secret.
The runtime was not exploitable — every verifying decode pins `HS256`, every key is symmetric —
but an advisory with no fix can only be accepted forever, and it is jose's second incomplete
fix of the same guard. pip-audit (a required check) was red on every PR from the day it landed
in OSV.
- **Tokens are unchanged on the wire.** Header, claim order and signature are byte-identical;
sessions, verification links and reset links minted before the upgrade verify after it
(pinned by jose-minted fixtures in `tests/unit/test_jwt_library_migration.py`).
- **`ecdsa`'s accepted finding (CVE-2024-23342) is gone with it** — jose was its only reason to
be installed. `security-audit.yml` now carries no exemptions.
- **`pymongo` 4.18.1 → 4.18.2** in the same change: `CVE-2026-96747/96748/96749` (forced local
socket via a `.sock` KMS endpoint, connection redirection via percent-encoded host delimiters,
a heap out-of-bounds write in BSON encoding) landed in OSV after jose did, so either fix alone
would still have left the required check red.

### Changed — Boot Smoke boots the checkout with the checkout's declared dependencies (#810, DEC-098)

- Boot Smoke runs the checkout's source (`PYTHONPATH: .`) over the published wheel, so the wheel
supplied only its dependency set. A PR whose code needed a dependency the release lacked failed
this required check, and only a release could clear it. #810 hit it first: replacing
python-jose with PyJWT failed with `No module named 'jwt'`.
- A step now installs the checkout's `[project].dependencies` on top of the wheel and logs the
`pip freeze` diff. At release the checkout is the tag, so nothing moves and the check proves
what it proved before.

### Changed — dependency bumps: starlette 1.7.0, pyphen 0.18.1, regex, mako, charset-normalizer; ui-kit 2.1.1 (#811)

- Runtime pins: `starlette` 1.6.0 → 1.7.0, `pyphen` 0.17.2 → 0.18.1, `regex` 2026.7.19 →
2026.9.29, `mako` 1.4.1 → 1.4.3, `charset-normalizer` 3.5.1 → 3.5.2.
- Platform UI: `@aindy/ui-kit` 2.1.0 → 2.1.1; `vite` 8.3.1 → 8.3.2 (dev); `source-map-js` 1.2.1 → 1.2.2 (transitive, #812).
- Native crate (build-time only): `pyo3` 0.29.2 → 0.29.3, `uuid` 1.26.1 → 1.27.0.

### Fixed — every request with plugins loaded paid ~3 s for a version check (`FR-52`, #814)

- DEBT-COMPAT-1's consumer check (2.25.0) ran at the end of every `load_plugins()` call, and the
registry's getters call `load_plugins()` lazily, 26 times in one warm `GET /memory/nodes`. Each
run read every installed distribution's metadata. The app measured p50 3,780 ms as shipped vs
820 ms with the check stubbed out. It now runs once per plugin module set, so a different
manifest or profile is still checked. `/api/version` reports the same records.
- Our own suites never saw it: runtime-only boot loads no plugins, so `load_plugins()` returned
before the check. The regression test boots a plugin manifest and counts metadata scans across a
warm request through the real route (26 before, 0 after).

### Changed — PyJWT is pinned with its `crypto` extra (#814)

- PyJWT's guard against asymmetric key material under an HMAC algorithm returns early when
`cryptography` is absent. The runtime had `cryptography` through a separate pin; the pin is now
`PyJWT[crypto]==2.15.1`, so the guard no longer depends on it, and a test asserts it is live.


## 2.26.0 — 2026-10-04

**Operator notes — read before upgrading.** Handoff: `docs/upgrades/APP_HANDOFF_v2.26.0.md`.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ DEC-001..009 are the founding principles. From DEC-010 on, one line per id
| Runtime contracts (idempotency, sandbox, connector, SDK, UI, invariants, durable-state ownership) | `docs/runtime/*_CONTRACT.md`, `EXECUTION_INVARIANTS.md`, `SECURITY_MATRIX.md`, `SYSCALL_REFERENCE.md`, `NODUS_DEVELOPER_GUIDE.md` |
| Design records index (every scope/design doc + status) | `docs/design/README.md` |
| Comparative research index (8 systems; what is settled) | `docs/governance/COMPARATIVE_RESEARCH_INDEX.md` |
| Release checklist; upgrades index; latest app handoff | `docs/governance/RELEASE_CHECKLIST.md`; `docs/upgrades/README.md`; `docs/upgrades/APP_HANDOFF_v2.26.0.md` |
| Release checklist; upgrades index; latest app handoff | `docs/governance/RELEASE_CHECKLIST.md`; `docs/upgrades/README.md`; `docs/upgrades/APP_HANDOFF_v2.27.0.md` |
| Outbound handoffs to Nodus | `docs/handoffs/README.md` |
| Route ownership; deployment targets | `docs/runtime/ROUTE_OWNERSHIP_INVENTORY.md`; `docs/operations/DEPLOYMENT_TARGETS.md` |
| Sibling repos | ui-kit `C:\dev\aindy-ui-kit\src\`; apps monolith `C:\dev\aindy-apps-monolith\CLAUDE.md` |
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# SPA dist via package-data. `packaging` is a declared dependency but pip
# treats it as a bootstrap package and skips it in --prefix installs; force
# it in so it propagates to the runtime stage.
RUN pip install --prefix=/install "aindy-runtime==2.26.0" \
RUN pip install --prefix=/install "aindy-runtime==2.27.0" \
&& pip install --prefix=/install --ignore-installed "packaging>=24.0"


Expand Down
20 changes: 0 additions & 20 deletions changelog.d/00-810-jwt-pyjwt-migration.md

This file was deleted.

9 changes: 0 additions & 9 deletions changelog.d/810-boot-smoke-checkout-deps.md

This file was deleted.

6 changes: 0 additions & 6 deletions changelog.d/811-deps-batch-2026-10-05.md

This file was deleted.

16 changes: 0 additions & 16 deletions changelog.d/814-fr52-consumer-check-once.md

This file was deleted.

108 changes: 108 additions & 0 deletions docs/upgrades/APP_HANDOFF_v2.27.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: "App Handoff — Runtime v2.27.0"
api_version: "1.0"
last_verified: "2026-10-09"
status: current
owner: "platform-team"
---

# App handoff: runtime v2.27.0

Pin bump `constraints.txt` `==2.26.0` → `==2.27.0` (your contract test moves the floor with it).
No ui-kit release: `@aindy/ui-kit` 2.1.1 stays current.

**What you need to do:** bump the pin and rebuild. There is no migration. Then take your four
`pip-audit` ignores off (§1), and re-measure the latency that FR-52 found (§2).

**What this release is.** It answers both of your open runtime FRs. **FR-51:** python-jose is gone
(replaced by PyJWT) and pymongo is 4.18.2. **FR-52:** the per-request version check now runs once.
The rest is dependency bumps.

> ## ★ Confirm what you are actually running: in the container, printing the path.
>
> ```bash
> docker exec <api-container> python -c "import AINDY, AINDY._version as v; print(v.__version__, list(AINDY.__path__))"
> # expect: 2.27.0 ['/usr/local/lib/python3.11/site-packages/AINDY']
> ```

---

## 0. Schema: none

No change under `AINDY/db/models/`. Alembic head stays **`0020`**, schema contract **`2026-09-20`**.
`bootstrap-schema` exits 0 on a current stack. The `Upgrade Path Guard` passes **trivially** on a
release like this, because there is no drift to find. Its negative control is the half that carries
meaning, and it was green.

## 1. FR-51: python-jose → PyJWT, pymongo 4.18.2 (#810, #814)

- **python-jose 3.5.0 is no longer installed.** CVE-2026-85394 (critical) has no fixed release, so
the library was replaced by **`PyJWT[crypto]==2.15.1`**. **`ecdsa`, `rsa` and `pyasn1` leave the
install with it.** If anything of yours imports one of those four without declaring it, it breaks
on this pin. We checked your source on 2026-10-09 and found none.
- **Tokens are unchanged on the wire.** Header, claim order and signature are byte-identical.
Sessions, verification links and reset links minted on 2.26.0 verify on 2.27.0. A jose-minted
fixture of each is pinned in the runtime's tests, so nobody is logged out by the upgrade.
- **`pymongo==4.18.2`** — CVE-2026-96747 / 96748 / 96749, exactly the three you listed.
- **PyJWT carries its `crypto` extra.** Its HMAC guard against asymmetric key material returns early
without `cryptography` (nodus-auth found this, def3ef8). The runtime had `cryptography` through a
separate pin anyway; now the guard does not depend on that.
- **The runtime's `security-audit.yml` carries no exemptions now.** The ecdsa acceptance
(CVE-2024-23342) went with jose.

**Ask:** remove your four FR-51 ignores (`GHSA-qx36-8mw2-4r3x`, `GHSA-v4x9-3549-crwv`,
`GHSA-vp6j-j7w5-5xjj`, `GHSA-3qf3-8w2g-rqmx`). Their stated removal condition is this adoption.

## 2. FR-52: the per-request ~3 s is gone (#814)

DEBT-COMPAT-1's consumer check ended every `load_plugins()` call, and the registry's getters call
that lazily: 26 times in one warm `GET /memory/nodes`. Each call read every installed
distribution's metadata. **It now runs once per plugin module set.** A different manifest or profile
is still checked, and `/api/version` serves the same `compatibility.consumers` records.

**Why our suites never saw it:** runtime-only boot loads no plugins, so `load_plugins()` returned
before reaching the check. The regression test now boots a plugin manifest and counts metadata scans
across one warm request through the real route: 26 before, 0 after. Your measurement is the one
that matters.

**Ask:** re-run your FR-52 measurement on 2.27.0. Use `GET /apps/scores/me` warm, the way you took
it (p50 3,780 ms as shipped vs 820 ms stubbed). Your soak row 2 latency check should pass again. The
`idle in transaction` you saw during live requests (2–5, each about one FR-52-length request) should
shrink with it.

## 3. Dependency bumps (#811, #813)

Runtime pins: `starlette` 1.7.0, `pyphen` 0.18.1, `regex` 2026.9.29, `mako` 1.4.3,
`charset-normalizer` 3.5.2. Platform UI: `@aindy/ui-kit` 2.1.1 (the version you already run),
`vite` 8.3.2, `source-map-js` 1.2.2. Native crate (build-time only): `pyo3` 0.29.3, `uuid` 1.27.0.
Nothing in the runtime imports `pyphen`. If you use it, its minor version moved.

**Release notes read, for each pin that moved. Three have a consequence worth naming:**

- **`regex`**: the 2026.7.19 → 2026.9.29 span fixes memory-safety bugs. These are a heap
out-of-bounds write at pattern compile time (issue 611), a `count_one()` underflow (612) and a
`Match.expand()` segfault (619). Nothing in the runtime or your source imports `regex`; it is a
declared pin with no importer. It is not reachable from here, but take the bump if you add one.
- **`mako` 1.4.2**: closes a directory-traversal bypass in `TemplateLookup` (`C:/../../x`). It
applies on **Windows** only. Mako runs only as Alembic's script template, so this is not reachable.
- **`starlette` 1.7.0**: background tasks now run only after the response is sent, **when
`BaseHTTPMiddleware` is in the stack.** Neither repo uses `BaseHTTPMiddleware` (the runtime
avoids it on purpose: `spa_fallback.py`). Your `BackgroundTasks` in `task_router.py` keep their
current timing. Also new: invalid multipart input answers 400, and `anyio>=4` is now required
(already met).

## 4. For the record: what your 2.25.0 readout unlocked

Your readout on 2026-10-09 met DEC-083's condition. `aindy_memory_recall_failures_total` read zero for
seven days, across 405 requests through the flagged path, with no pool exhaustion. **That clears
`AINDY_MEMORY_RECALL_OWN_SESSION` to default ON.** That flip is **not** in this release. It will
ship in its own release, with its own handoff, followed later by pgvector 0.5.0 (MEM-EXPAND-DEAD-1).
You already run the flag on, so it will change nothing for you.

## Asks, in one place

1. Pin `==2.27.0`, rebuild, and confirm the version and path with the command at the top.
2. Remove the four FR-51 `pip-audit` ignores (§1).
3. Re-run the FR-52 latency measurement (§2) and report it. That closes FR-52 on your side.
4. **Still owed from 2.26.0:** after your first real agent runs, grep for
`RESOURCE_LIMIT_EXCEEDED` and report any hit with the run's step count.
Loading
Loading