diff --git a/AINDY/_version.py b/AINDY/_version.py index 23077358..07150ef8 100644 --- a/AINDY/_version.py +++ b/AINDY/_version.py @@ -1 +1 @@ -__version__ = "2.26.0" +__version__ = "2.27.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9740811b..17056810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/CLAUDE.md b/CLAUDE.md index 28e906c9..b31cb8d9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` | diff --git a/Dockerfile b/Dockerfile index f0b1c6c0..44160272 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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" diff --git a/changelog.d/00-810-jwt-pyjwt-migration.md b/changelog.d/00-810-jwt-pyjwt-migration.md deleted file mode 100644 index ec8c2930..00000000 --- a/changelog.d/00-810-jwt-pyjwt-migration.md +++ /dev/null @@ -1,20 +0,0 @@ -### 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) has been red on every PR since 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. diff --git a/changelog.d/810-boot-smoke-checkout-deps.md b/changelog.d/810-boot-smoke-checkout-deps.md deleted file mode 100644 index 9ab63c3a..00000000 --- a/changelog.d/810-boot-smoke-checkout-deps.md +++ /dev/null @@ -1,9 +0,0 @@ -### 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. diff --git a/changelog.d/811-deps-batch-2026-10-05.md b/changelog.d/811-deps-batch-2026-10-05.md deleted file mode 100644 index 8e38181b..00000000 --- a/changelog.d/811-deps-batch-2026-10-05.md +++ /dev/null @@ -1,6 +0,0 @@ -### 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. diff --git a/changelog.d/814-fr52-consumer-check-once.md b/changelog.d/814-fr52-consumer-check-once.md deleted file mode 100644 index 907d6e46..00000000 --- a/changelog.d/814-fr52-consumer-check-once.md +++ /dev/null @@ -1,16 +0,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. diff --git a/docs/upgrades/APP_HANDOFF_v2.27.0.md b/docs/upgrades/APP_HANDOFF_v2.27.0.md new file mode 100644 index 00000000..8b9479e2 --- /dev/null +++ b/docs/upgrades/APP_HANDOFF_v2.27.0.md @@ -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 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. diff --git a/docs/upgrades/README.md b/docs/upgrades/README.md index 7ead6d47..c9871c0f 100644 --- a/docs/upgrades/README.md +++ b/docs/upgrades/README.md @@ -1,6 +1,6 @@ --- title: "Upgrading aindy-runtime" -last_verified: "2026-10-04" +last_verified: "2026-10-09" api_version: "1.0" status: current owner: "platform-team" @@ -68,6 +68,7 @@ loop, not a warning (`FR-14`, 2.1.0). Read the exit code — `0` done, `3` re-ru | 2.21.0 | 2026-09-18 | [`APP_HANDOFF_v2.21.0.md`](./APP_HANDOFF_v2.21.0.md) | none | `0019` | `2026-09-16` | no | Pin bump + rebuild. **★ Behaviour change: a pipeline route handler that raises is now ROLLED BACK before `execution.failed` (#721) — a write-then-raise-without-commit route used to keep the write; shared-session test suites must test 4xx routes on a private engine.** **`user.id` removed from `syscall.*` spans (dated in 2.20.0) → `enduser.id`.** `call_tool` gains an optional third argument; `replayed: true` on continued runs (#722). `EGRESS-INPROC-1` (flag-gated), `AUTHORITY-LIFETIME-1` (stale tokens refused `permission`), `AUDIT-CORRELATION-1` (additive event keys). DEC-046/047 accepted. **New ask: declare `nltk` + `textstat` in the app's own pyproject (`PACK-DEBT-6`) — the runtime will drop both pins after.** | | **2.23.0** | 2026-09-23 | [`APP_HANDOFF_v2.23.0.md`](./APP_HANDOFF_v2.23.0.md) | none | `0020` | `2026-09-20` | no | Pin bump + rebuild, **no schema step** (so the `Upgrade Path Guard`'s meaning rests on its **negative control** — say so when citing it). **FR-42**: the `agent_capability_mappings` FK WARNING on every non-agent token mint is gone; the token now carries `mapping_recorded` (#750, DEC-071). **FR-15** (distributed mode only): a FOLLOWER api used to queue a woken resume nothing drained — `woken: true` and a run `waiting` forever — now forwarded to the dispatcher (`aindy_scheduler_resume_forwarded_total`); and a worker that never opened its scheduler dropped every bus event past 1000 (#751, DEC-072). **New opt-in, default OFF: `AINDY_TOOL_IDEMPOTENCY_STRICT`** — without it, concurrent duplicates of an `EXACTLY_ONCE` TOOL all execute (measured: 5 concurrent sends → 5 real messages; 8-way → 8 runs); with it, 1 run + 7 replays (#755, `IDEM-13`). Dependency group #749 (`cloudpickle` newly pinned). **`nltk`/`textstat` drop after 2026-10-01 — declare them now.** Asks: the `nodus_vm` denial re-run (third release); a strict-mode window if you have concurrent `EXACTLY_ONCE` tool duplicates. | | **2.25.0** | 2026-10-01 | [`APP_HANDOFF_v2.25.0.md`](./APP_HANDOFF_v2.25.0.md) | none | `0020` | `2026-09-20` | no | Pin bump + rebuild, no schema step (the `Upgrade Path Guard`'s meaning rests on its negative control). **★ `AINDY_AUTHORITY_NEGOTIATION` defaults ON** (#771, DEC-081): a tool declaring `on_denial="wait"` parks on a denial instead of failing; only `0/false/no/off` disable it. **★ `nltk` / `textstat` no longer installed** (#784, PACK-DEBT-6) — declare them yourself; their four audit exemptions are gone. **FR-48** `result_schema` + plan-time path check (flag-gated; refused plan fails creation, DEC-077..079), **FR-49** `aindy_memory_recall_failures_total{site,stage}` (DEC-083), **FR-50** memory routes no longer 400 on an omitted optional field (also tags-only recall and node create without `node_type`), **DEBT-COMPAT-1** boot warns on a plugin's declared range; `compatibility.consumers` on `/api/version` (DEC-080). OpenTelemetry 1.45.0 / 0.66b0. Asks: `result_schema` on `research.query` / `search.query`; one soak window reading the recall-failure counter; reinstall the dev venv. | +| **2.27.0** | 2026-10-09 | [`APP_HANDOFF_v2.27.0.md`](./APP_HANDOFF_v2.27.0.md) | none | `0020` | `2026-09-20` | no | Pin bump + rebuild, no schema step (the `Upgrade Path Guard`'s meaning rests on its negative control). **★ python-jose → `PyJWT[crypto]`** (FR-51, CVE-2026-85394, no fix upstream): `ecdsa`, `rsa`, `pyasn1` leave the install; tokens byte-identical, sessions survive. **`pymongo` 4.18.2** (CVE-2026-96747/8/9). **FR-52:** the per-request ~3 s DEBT-COMPAT-1 metadata scan now runs once per plugin set. starlette 1.7.0, pyphen 0.18.1, regex, mako, charset-normalizer. Asks: drop the four FR-51 audit ignores; re-measure FR-52 latency. | | **2.26.0** | 2026-10-04 | [`APP_HANDOFF_v2.26.0.md`](./APP_HANDOFF_v2.26.0.md) | none | `0020` | `2026-09-20` | no | Pin bump + rebuild, no schema step (the `Upgrade Path Guard`'s meaning rests on its negative control). **★ Three defaults ON:** `AINDY_PLAN_STEP_REFERENCES` (FR-46, DEC-084), `AINDY_RUN_SCOPED_QUOTA` (a run's syscalls count against one `AINDY_QUOTA_MAX_SYSCALLS`, default 100; wall time not summed; DEC-095/096), `AINDY_FLOW_FAN_OUT` (DEC-097; the runner no longer holds a transaction across a superstep). **EFFECT-OUTCOME-UNKNOWN-1:** `unknown`/`partial` held on replay, `AT_MOST_ONCE`, `outbound_request` no longer retries a possibly-processed POST/PATCH, admin `GET /platform/effects/unknown` + `POST /platform/effects/{action_id}/resolve`. **FS-SCOPE-1:** declared filesystem scope enforced on both seams (Python, not kernel). On-demand latency floor. Asks: grep `RESOURCE_LIMIT_EXCEEDED` after first runs; the recall-failure readout (>= 10-09) before rebuilding. | | **2.24.0** | 2026-09-26 | [`APP_HANDOFF_v2.24.0.md`](./APP_HANDOFF_v2.24.0.md) | none | `0020` | `2026-09-20` | no | Pin bump + **`@aindy/ui-kit` 2.1.1** + rebuild, no migration — but **FR-43**: `bootstrap-schema` now sees a column widening (a stack still at `system_events.source` 32 exits **3**; corrects the 2.22.0 handoff's exit-3 promise). **FR-44** resume arms the wait (`run_status` may be `"waiting"`), **FR-45** runtime adapters stamp, **FR-46** step references (flag off), **IDEM-14** tool key per step (`execute_tool(step_index=)` — stubs), **FR-47** ui-kit `timeoutMs` (a caller's abort is no longer 408). nodus-lang 5.15.0. Asks: FR-46 evidence run, FR-44 two-process re-run. | | 2.19.0 | 2026-09-16 | [`APP_HANDOFF_v2.19.0.md`](./APP_HANDOFF_v2.19.0.md) | none | `0018` | `2026-09-10` | no | Fixes the runtime half of the app's `SYSCALL-SILENT-ERRORS-1` (#692): the system-state snapshot now counts agent runs — expect `active_runs` to rise. **Removes `sys.v1.agent.list_recent_durations`** (#693, DEC-022; app never called it). New `AINDY_NODUS_MAX_MEMORY_MB`, unset by default (#697). Plugin-host post-launch kill on every path (#694). Re-entrant `agent.undo` (#696). DEC-021: no `waiting → completed` edge — the app's paused-task hook keeps working; two truthful shapes offered. Pin bump + rebuild. |