diff --git a/AGENTS.md b/AGENTS.md index 9a2d212..6c5f925 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,4 +49,6 @@ All build tiers and v1 gates are `tested` (37 of 39 requirements): `model` → ` - **`nvm use default` puts a Node 24 on PATH** for `bun run mutate` / focused `stryker run` invocations. - **`bun test ` may exit 1 with zero failures** — the per-file coverage floor (bunfig.toml) judges partially-imported files. Exit 1 with 0 fails = coverage floor, not a test failure; gate on full `bun test` runs. - **Internal imports**: upward reaches (anything needing `../`) use `#src/…`/`#scripts/…` (package.json `imports`); same-directory and downward stay relative, explicit `.ts` extensions. Enforced by `test/import-style.test.ts` (D-013). +- **Never run `biome migrate` unattended** (D-021). On this repo's v1 config it rewrites `"rules": { "recommended": true }` as `"rules": { "preset": "none" }`, which deletes the rule set instead of preserving it: `biome check .` then exits 0 on code containing `any`, `==` and unused vars, so the lint gate dies silently and CI stays green. The correct v2 spelling is `"preset": "recommended"`. After any biome config change, re-verify with a planted violation and check the **exit code**, not the printed summary. +- **Dependency bumps: refresh ≠ range change.** Taking a newer build of an already-declared range is routine; requiring a version you previously did not is a decision. `bun update` conflates them — it rewrites `package.json` floors even for packages whose version did not move — so refresh with `bun update`, then `git checkout package.json && bun install` to keep the change lockfile-only. Range changes get their own entry in `DECISIONS.md`, their own PR, and a measurement (D-020, D-021). - **CI (GitHub Actions)**: `.github/workflows/ci.yml` runs the gate set (`check-docs` → `lint` → `typecheck` → `demo-app:build` → full `bun test`) on PRs and main pushes; main pushes also upload `demo-app/dist/` + `coverage/` artifacts. Bun is pinned there (1.3.14) so the bunfig coverage-gate semantics stay as verified; bump the pin deliberately, in its own PR. A `main` ruleset requires the `gates` check (repo-admin bypass keeps direct pushes possible). Mutation testing stays out of CI (D-017). diff --git a/DECISIONS.md b/DECISIONS.md index 671b2a4..75dfdd3 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -172,3 +172,20 @@ Append-only. Each decision has a stable never-reused `D-###` id, what was decide **Obligations**: none deferred. The L1 tuple fork remains open exactly as D-018 left it, now with a third option on the table. **From**: release review of json-schema-faker v0.6.3 (2026-08-01); every claim above measured against the installed toolchain, none taken from the changelog. **Folds into**: package.json, docs/specs/build-plan.md §1, docs/specs/design.md §4, docs/specs/contracts.md §3, REQUIREMENTS.md (R-027), src/engine/faker.ts, src/registry/index.test.ts, test/spikes/jsf-fidelity.test.ts, DECISIONS.md (D-003 premise note) + +### D-021: Take the five in-range dependency refreshes; decline mqtt-pattern 2.x, defer aedes 1.x, biome 2.x and TypeScript 7 as their own decisions +**Date**: 2026-08-01 +**What**: Refresh the five dependencies whose newest release already satisfies the declared range — `@asyncapi/parser` 3.6.0→3.6.1, `hono` 4.12.27→4.12.33, `mqtt` 5.15.1→5.15.2, `@types/react` 19.2.17→19.2.18, `@types/react-dom` 19.2.3→19.2.4 — as a **lockfile-only** change, with `package.json` untouched. The four remaining outdated packages each need a range change, and each is decided separately rather than swept in: **`mqtt-pattern` 2.1.1 is declined outright**; **`aedes` 1.1.1 is deferred behind R-006/R-007**; **`@biomejs/biome` 2.5.6 and `typescript` 7.0.2 are both viable and both deferred to their own PRs**. +**Why**: A version bump and a range change are different acts. The first says "take the newest build of what we already declared"; the second says "we now require something we previously did not". Only the first is routine, so only the first is bundled. `bun update` does not respect that line — it rewrote seven `package.json` floors as a side effect, including for `ajv`, `yaml`, `react`, `react-dom`, `typescript` and `biome`, none of which changed version at all. Those floors are declared intent, so they were restored and the lockfile refresh kept on its own. +**Measured (2026-08-01, installed toolchain, Bun 1.3.14)**: +- **The refresh is gate-clean**: `check-docs`, `lint`, `typecheck`, `demo-app:build` and full `bun test` each exit 0, against a same-command baseline taken on `main` before any change. `@asyncapi/parser` 3.6.1 adds a v3 channel-parameter-vs-address-placeholder validation rule, which is the one item here that could have rejected a previously-accepted fixture; it does not. +- **`mqtt-pattern` 1.2.0 → 2.1.1 ships a byte-identical runtime**: `index.js` in the two tarballs has the same sha256 (`63fbbfd2…`) once CRLF/LF is normalized, and the whole major is the addition of `.d.ts` files plus a `ts-toolbelt` **production** dependency. +- **v2's types are strictly worse for offbook's call shape**: `exec` becomes `exec

(p: P, t: string): MqttParameters

| null`, and offbook's patterns are computed at runtime (`toPattern(c.topic)`), so `P` infers as the general `string` and `MqttParameters` resolves to a type with no properties. Reading a captured parameter off it fails with `TS7053: Property 'deviceId' does not exist on type 'MqttParameters'`. The repo typechecks under v2 only because the 9-line ambient shim in `src/registry/mqtt-pattern.d.ts` shadows the shipped types, or because the existing `as Record` cast absorbs them — both verified. Deleting that shim is the only thing the bump could buy, and the shim is the more accurate declaration. +- **`aedes` 0.51.3 → 1.1.1 breaks offbook's exact construction call, by design**: 1.x is `"type": "module"`, `engines.node >= 20`, exports `class Aedes` as a **named** export, and sets its **default** export to a function whose entire body throws ``Aedes default export has been removed. Use 'const aedes = await Aedes.createBroker()' instead.`` `src/broker/index.ts` does `import Aedes from "aedes"` and `new Aedes({...})`, so it hits that throw. The constructor also becomes `static async createBroker(opts)`, and `drainTimeout` changes default from `0` to `60000`. +- **`@biomejs/biome` 2.5.6 is viable, but its own `migrate` command silently disables the lint gate** — see below. With the config corrected by hand it is clean-ish: 17 `assist/source/organizeImports` (named-import sorting, auto-fixable churn from v2's import-organizer overhaul), 14 `lint/correctness/noUnsafeOptionalChaining`, and 4 assorted. The transport-isolation rule survives the move out of `nursery` into `style` and was re-verified to still error outside `src/broker/` and stay exempt inside it. +- **`typescript` 7.0.2 typechecks the repo cleanly and about 10x faster**: `tsc --noEmit` exits 0 and takes 0.11s wall against 5.9.3's 1.12s. Gate liveness was confirmed separately (a planted `const n: number = "…"` still exits 1), so the pass is a real pass and not a silently-skipped check. +**Mitigations / notes**: **`biome migrate` is not safe to run unattended on this repo.** Pointed at the v1 config it rewrites `"rules": { "recommended": true }` as `"rules": { "preset": "none" }`, which does not preserve the rule set — it removes it. Verified by measurement, not by reading: a file containing `any`, `==` and an unused `var` draws **zero** diagnostics under the migrated config and four (`noExplicitAny` ×2, `noDoubleEquals`, `noUnusedVariables`) once `preset` is corrected to `"recommended"`. The failure mode is the dangerous kind — `biome check .` exits 0, CI stays green, and the lint gate is gone. Whoever takes the biome 2 PR must correct that line by hand and re-verify with a planted violation. The 14 `noUnsafeOptionalChaining` findings are real but low-severity and confined to test files, all the same `(x[0]?.payload as T).prop` shape: a `?.` guard immediately dereferenced, so a genuine failure surfaces as a confusing `TypeError` instead of a clean assertion diff. They are worth fixing on their own merits and are not a reason to hold the bump. +**Consequences for earlier entries**: none. D-020's measured-bump rule ("a bump is taken only when it is measured not to move the R-027 corpus") is unaffected — none of the five refreshed packages is the faker, and `json-schema-faker` stays exactly pinned at `0.6.3`. +**Obligations**: three deferred, none blocking. **(1)** `aedes` 1.x stays deferred until R-006 (WS fidelity) and R-007 (capture the browser application's `connect()`) have run, because both spikes measure `broker/`'s **current** Aedes defaults and the ws stack underneath them; moving that floor first would invalidate the thing being measured, and `aedes-server-factory` — last published 2021-06-28, four years before this entry and nearly three years before aedes 1.0 — is not a maintained path to 1.x either. **(2)** `@biomejs/biome` 2.5.6 as its own PR, carrying the hand-corrected config and the ~34 code changes. **(3)** `typescript` 7.0.2 as its own PR: it is a three-week-old major of the compiler that gates CI, so it earns a deliberate decision rather than a quiet range change, and the ~10x speedup is a real reason to take it soon. +**From**: a dependency review on 2026-08-01 covering all nine outdated packages; every behavioral claim above measured against the installed toolchain or the published tarballs, none taken from release notes. +**Folds into**: bun.lock, AGENTS.md (working notes) diff --git a/bun.lock b/bun.lock index 6aa002e..d0aa40c 100644 --- a/bun.lock +++ b/bun.lock @@ -31,7 +31,7 @@ }, }, "packages": { - "@asyncapi/parser": ["@asyncapi/parser@3.6.0", "", { "dependencies": { "@asyncapi/specs": "^6.11.1", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json": "3.21.0", "@stoplight/json-ref-readers": "^1.2.2", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.18.3", "@stoplight/spectral-functions": "^1.7.2", "@stoplight/spectral-parsers": "^1.0.2", "@stoplight/spectral-ref-resolver": "^1.0.3", "@stoplight/types": "^13.12.0", "@types/json-schema": "^7.0.11", "@types/urijs": "^1.19.19", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "avsc": "^5.7.5", "js-yaml": "^4.1.1", "jsonpath-plus": "^10.0.7", "node-fetch": "2.6.7" } }, "sha512-6S0Yr8vI418a1IrpGsOYbfWVo9+aHvSqN2oSkiY0YJltS/C7oDOt9e0mo6hSld8bg+EeKrtgkVmpW4obh1JFvA=="], + "@asyncapi/parser": ["@asyncapi/parser@3.6.1", "", { "dependencies": { "@asyncapi/specs": "^6.11.1", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json": "3.21.0", "@stoplight/json-ref-readers": "^1.2.2", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.18.3", "@stoplight/spectral-functions": "^1.7.2", "@stoplight/spectral-parsers": "^1.0.2", "@stoplight/spectral-ref-resolver": "^1.0.3", "@stoplight/types": "^13.12.0", "@types/json-schema": "^7.0.11", "@types/urijs": "^1.19.19", "ajv": "^8.18.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "avsc": "^5.7.5", "js-yaml": "^4.3.0", "jsonpath-plus": "^10.0.7", "node-fetch": "2.6.7" } }, "sha512-pqacvPdtkCI6DUOHxMfhumaqRwXAGaV05JzHIP3CxF3Jd3EMu25PWqsiVmbeLf8YDCy0K5UWm4NXW6TpwXwlSw=="], "@asyncapi/specs": ["@asyncapi/specs@6.11.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-A3WBLqAKGoJ2+6FWFtpjBlCQ1oFCcs4GxF7zsIGvNqp/klGUHjlA3aAcZ9XMMpLGE8zPeYDz2x9FmO6DSuKraQ=="], @@ -221,9 +221,9 @@ "@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="], - "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], "@types/readable-stream": ["@types/readable-stream@4.0.24", "", { "dependencies": { "@types/node": "*" } }, "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg=="], @@ -431,7 +431,7 @@ "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], - "hono": ["hono@4.12.27", "", {}, "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q=="], + "hono": ["hono@4.12.33", "", {}, "sha512-+SwvkaiJtxsiPjhy9LivY/1m7UsNqCJetM1BrZl9A5DkQhlbHQDU730mMiDPWjnoCYOM8Chf3WrCJw27kNTPFQ=="], "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], @@ -549,7 +549,7 @@ "mqemitter": ["mqemitter@6.0.2", "", { "dependencies": { "fastparallel": "^2.4.1", "qlobber": "^8.0.1" } }, "sha512-8RGlznQx/Nb1xC3xKUFXHWov7pn7JdH++YVwlr6SLT6k3ft1h+ImGqZdVudbdKruFckIq9wheq9s4hgCivJDow=="], - "mqtt": ["mqtt@5.15.1", "", { "dependencies": { "@types/readable-stream": "^4.0.21", "@types/ws": "^8.18.1", "commist": "^3.2.0", "concat-stream": "^2.0.0", "debug": "^4.4.1", "help-me": "^5.0.0", "lru-cache": "^10.4.3", "minimist": "^1.2.8", "mqtt-packet": "^9.0.2", "number-allocator": "^1.0.14", "readable-stream": "^4.7.0", "rfdc": "^1.4.1", "socks": "^2.8.6", "split2": "^4.2.0", "worker-timers": "^8.0.23", "ws": "^8.18.3" }, "bin": { "mqtt_pub": "build/bin/pub.js", "mqtt_sub": "build/bin/sub.js", "mqtt": "build/bin/mqtt.js" } }, "sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA=="], + "mqtt": ["mqtt@5.15.2", "", { "dependencies": { "@types/readable-stream": "^4.0.21", "@types/ws": "^8.18.1", "commist": "^3.2.0", "concat-stream": "^2.0.0", "debug": "^4.4.1", "help-me": "^5.0.0", "lru-cache": "^10.4.3", "minimist": "^1.2.8", "mqtt-packet": "^9.0.2", "number-allocator": "^1.0.14", "readable-stream": "^4.7.0", "rfdc": "^1.4.1", "socks": "^2.8.6", "split2": "^4.2.0", "worker-timers": "^8.0.23", "ws": "^8.18.3" }, "bin": { "mqtt_pub": "build/bin/pub.js", "mqtt_sub": "build/bin/sub.js", "mqtt": "build/bin/mqtt.js" } }, "sha512-VWZU2CSUY3U3oN0PSBRDE5SNsFi4zqqNeQ/uv3pZWqY3CrBXD/dhd0ZLjlsk5YnebGlrapi4lRVNJPUNQ5aZ3w=="], "mqtt-match": ["mqtt-match@1.0.3", "", {}, "sha512-nfeAp+chyjVeIvvrgMhQCfDAIVp/zXX8rtxHQwuAWuapqAdFs1F0kIekG445ps3xs/qFPK6l2xRlAyiqqwbmrQ=="],