v3.2.5
actix/actix-webv3.2.5Jul 8, 2026by github-actions[bot]
AI Summary
This release introduces coordination locks for placement-partition, environment-variable indirection for pack paths, review-time regression gates, and fixes for stale assertions and review prompt files.
Key Highlights
- Coord/primary placement-partition lock formalized as the single authority.
- Doctrine/charter pack paths support environment-variable indirection.
- Review-time regression gate at `move-task --to for_review`.
- Post-merge stale-assertion analyzer fixes false-positive storms.
New Features
- Placement-partition coordination locks
- Environment-variable indirection for pack paths
- Review-time regression gates
Full Release Notes
### ✨ Added
- **Coord/primary placement-partition lock — one topology-aware seam owns where
every mission artifact is stored and read (#1716, #1878/G2).** Formalizes
`PlacementSeam.write_target(kind)` / `read_dir(kind)` as the single thin
authority over the existing `resolve_action_context` SSOT and routes every
remaining write site through it — **fail-closed** via `PlacementResolutionRequired`
(a real resolution failure raises rather than silently committing to the
operator's checkout). An architectural ratchet forbids the
`CommitTarget(ref=<checkout>)` grammar (a self-test proves it bites), and an
end-to-end characterization test locks the behaviour. Canonical partition:
coord branch = lifecycle (status/notes/trace/issue-matrix/move-task); primary
= stable planning (spec/plan/WP outlines); no-coordination topology → all
primary. RETROSPECTIVE delegates to the existing `resolve_retrospective_home`
(no second authority). Also **fixes #2091** (an empty `mid8` no longer builds a
malformed `kitty/mission-<slug>-` coordination branch → `git worktree add`
exit-128; the `CoordinationWorkspace` composition seam fail-loud-guards it) and
**#2250** (a never-coordinated mission no longer reports
`COORDINATION_BRANCH_DELETED`). **The read side is now locked too (#1716
closeout):** every remaining coordination-surface *read* — the kind-blind
`resolve_feature_dir_for_mission` sweep (#2453) and the inline `meta.json`
reads (#2100) — routes through the same topology-aware seam kind-correctly, so
`spec-kitty accept` no longer reads a stale `-coord` acceptance matrix (#2404)
and planning artifacts always resolve to their canonical surface regardless of
the working directory. A non-vacuous architectural ratchet keeps the routed
read classes from silently regrowing. Closes epic **#1716** and #2453 / #2100
/ #2088 / #2404.
- **Doctrine/charter pack paths support environment-variable indirection
(#2437).** Pack-location fields (e.g. an org-pack's `local_path`) previously
had to be hardcoded absolute paths, so a shared, committed `.kittify` config
resolved only on the machine of the developer who wrote it and broke for every
teammate and CI runner. Pack-path templates now expand `${VAR}`/`$VAR` and `~`
at read time (via `os.path.expandvars` + `expanduser`), so a portable form
like `${SPEC_KITTY_PACK_HOME}/acme-doctrine` can be committed once and each
environment resolves it against its own base. Expansion is **fail-closed**: an
unset/empty env token raises rather than silently collapsing to a wrong path
(e.g. `${UNSET}/acme` → repo root). Purely additive — literal absolute paths
keep working.
- **Review-time regression gate at `move-task --to for_review` (#572).** When a
work package moves to `for_review`, Spec Kitty now auto-scopes the CI shards
that cover the WP's changed files and re-runs them, so a WP that broke a
shared contract pinned by a test *outside* its `owned_files` is caught at
review time instead of only at merge. Warn-only by default; opt in to a hard
block with `review.fail_on_pre_review_regression` (enforced only when
`review.test_command` is set; `move-task --force` overrides), and override the
scope per-WP via frontmatter `pre_review_test_scope`. See
[review-gates.md](../guides/review-gates.md).
- **`spec-kitty review --check-residual` + environment-parity preflight
(#2283).** The new `--check-residual` flag runs CI's always-on
`unit-contract-residual` `-m` selection over `tests/` locally — the `-m`
expression is read **live** from `.github/workflows/ci-quality.yml`, so a
previously CI-only marker-orphan failure can be reproduced before pushing. The
`spec-kitty review` preflight also detects local-vs-CI environment skew
(installed package versions diverging from `uv.lock`, plus typer/click
lock-parity), warning by default and failing closed under
`SPEC_KITTY_ENV_SKEW_FAIL_CLOSED`.
### 🐛 Fixed
- **Post-merge stale-assertion analyzer stops false-positive storms on
behavior-preserving refactors (#2031, #2343).** A relocation/re-export (a
WP05 extraction produced 180 false findings and tripped the drift ceiling)
and generic-literal noise both flagged as stale assertions. The analyzer now
(a) suppresses a removed identifier only when the origin file's **module-level
head** still re-exports/imports it (keyed on head-importability — not
bare-name-anywhere, so a genuine deletion of a common name like `run`/`main`
is still flagged; a nested `import parse` does not mask a real `def parse()`
deletion), and (b) suppresses literal-only noise by **genuineness** (a pinned
generic-token set / all-punctuation), never by length (a short literal like
`"E001"` can be assert-critical). Both paths suppress rather than
info-downgrade, so render surfaces and the FP ceiling are unchanged.
- **Review-prompt files no longer accumulate unbounded; coverage-allowlist
repointed off a removed module (#2439, #2443).**
`write_review_prompt_with_metadata()` now prunes review-prompt files to a
newest-preserving cap after each write — fail-safe, and never pruning the
current invocation's own file — closing the LC-7 retention residual of #1842.
Separately, the `diff-coverage` critical-path `--include` allowlist still
referenced the stale `src/specify_cli/core/mission_detection.py` (removed in a
rename); it is repointed to `src/specify_cli/lanes/branch_naming.py` in **both**
the CI workflow and its test authority in lockstep, with a glob-aware existence
guard so a phantom allowlist entry reds instead of silently covering nothing.
- **`spec-kitty agent mission finalize-tasks` accepts glob-only `owned_files`
(#2446).** A work package whose `owned_files` used a filename glob (e.g.
`src/foo/*.py`) with no explicit `authoritative_surface` was rejected because
`infer_authoritative_surface` produced an invalid `src/foo/*.py/` path. It now
reduces a glob-bearing final path segment to its directory, so glob-only
ownership infers a valid authoritative surface. (Fix landing via PR #2454.)
- **A retired skill's projected files are now drained by surface repair instead
of orphaned forever (#2409).** When a skill was retired from the registry,
`spec-kitty upgrade --project` could not reconcile its leftover projections:
the repair path warned `Cannot repair … skill not found in registry` on every
subsequent upgrade and left the files in place (committed, where the repo
tracks skill surfaces; a dangling symlink, where the global root had dropped
the skill). Repair now reconciles retirements **manifest-driven**: entries in
`.kittify/skills-manifest.json` whose skill no longer exists in a *live*
registry have their projected files removed — symlinks unlinked, hash-clean
copies deleted, user-**modified** copies archived to
`.kittify/.migration-backup/agent-skills/` instead of deleted — emptied skill
dirs pruned, and the manifest entries dropped, silencing the repeat warning.
Only ledger-recorded paths are ever touched (a user-authored skill sharing
the projection root is never scanned), and an **empty** registry never
retires anything (a broken canonical source must not mass-drain the
manifest).
- **Test-session state leaks closed: per-mission prompt-tmp namespace +
workspace-context tombstone (#1842, #2032).** Runtime prompt files are now
written under a per-repo/per-mission namespaced temp directory
(`<tmp>/spec-kitty-prompts/<repo-id>/…`) instead of a shared flat `/tmp`
path, and a lane's workspace-context sidecar is now tombstoned when its
worktree is torn down at merge (or when the lane is canceled) — clearing the
stale-context and prompt-litter residue flagged by the #1931 `/tmp`-hygiene
audit. A session-scoped pytest reaper (controller-gated, run-uid-scoped, and
never touching the real `~/.spec-kitty`) now fails the suite if a test leaks
repo-root residue, so new leaks are caught at their source.
- **Dashboard: PR-bound missions planned on a feature branch are visible again
(#2430).** The dashboard scanner resolved ONE directory per mission,
coord-worktree-first — but under coordination topology the two partitions
live on different surfaces: `spec-commit` lands planning artifacts
(`spec.md`/`plan.md`/`tasks.md`/`tasks/`/`meta.json`) on the **primary**
surface, while the live event log lives on the **coordination** branch. The
scanned `-coord` copy therefore held only status writes, so the legacy
existence filter (numeric prefix or `tasks/` present) silently dropped any
in-flight post-083 mission from `/api/features`, and artifact viewers/kanban
read the wrong surface. The scanner now splits the read per partition —
planning artifacts primary-first through the same kind-aware read seam the
#2331 identity fix uses (`resolve_planning_read_dir`), live lane state from
the gather-resolved coord surface — so an in-flight coordination mission
lists with its real spec/plan/tasks AND live kanban lanes. Same
coord-shadows-primary class as #2331; the accept-side sibling is #2404.
- **Session banner no longer recommends a downgrade (#2413).** The
session-presence health check decided "upgrade-available" with a bare
inequality (`avail != current`), so any machine whose installed CLI was
*newer* than the cached PyPI latest — a fresh release not yet in the 1-hour
cache, or an rc/dev install — got "⚠ Upgrade available: 3.2.2" while running
3.2.4. Both health branches now use `packaging.Version` ordering
(`avail > current`); unparseable versions are treated as no-upgrade so
session start never breaks on a weird cache value.
- **The shared Agent-Skills projection root and the per-machine skill install
ledger are now gitignored (#2412).** The skills installer projects global
canonical skills into `.agents/skills/` (codex/vibe/pi/letta) preferring
**absolute symlinks** into the user-global root — machine-local content by
construction — but unlike `.claude/` and the other agent dirs (gitignored
wholesale at init), bare `.agents/` was never covered by any init path,
registry entry, or migration, so the `/Users/<name>/...` symlink blobs were
committable (and the upgrade auto-commit would land them automatically).
`.kittify/skills-manifest.json` (per-machine timestamps/hashes/delivery
modes) had the same gap. Both are now registered `IGNORED` state surfaces
(fresh `spec-kitty init` gitignores them, and `spec-kitty doctor`'s
present-but-not-gitignored warning covers them), and a sibling backfill
migration (`3.2.5_agents_skills_gitignore_backfill`) adds the entries on
`spec-kitty upgrade` for already-initialised projects. The symlink-vs-copy
delivery question (ask 3 of #2412) is tracked separately on the issue.
- **`spec-kitty upgrade` now commits worktree churn on every checkout, not just
main (#2392, closes #2385/#1873, pins #2105).** The upgrade auto-commit
captured a porcelain baseline for the main checkout only, so migration writes
it made in sibling worktrees were left uncommitted — dirtying them and
blocking coordination-topology merges (NFR-002). The porcelain-baseline commit
routine is extracted into a canonical per-checkout seam
(`upgrade/autocommit.py::commit_touched_checkout`); the migration runner now
snapshots each worktree *before* its writes and commits only that new churn on
the worktree's own branch (in-flight WP edits are never swept in;
`manual_review_required` migrations skip the commit with a warning;
detached-HEAD skips instead of guessing a ref; `--dry-run` stays a strict
no-op). #1873's self-healing path is restored (metadata synthesized from
`None` is marked dirty so it's saved even when detected version == target).
- **Legacy-topology warning no longer over-fires on intentional
coordination-less missions (#2351).** The once-per-mission warning keyed on
`coordination_branch` absence, so it fired for genuinely deliberate
`single_branch`/`lanes` shapes (#2218) as well as truly pre-SSOT missions. A
new warning-only classifier gates just the emit on the canonical stored
`MissionTopology` + flattened flag (warn iff coordination branch absent AND
stored topology null AND not flattened; malformed still warns); the shared
`_is_legacy_mission` predicate that drives worktree routing and write-contract
selection is left byte-for-byte unchanged.
- **`.kittify/migrations/` and `.kittify/logs/` are gitignored and backfilled
(#2384, completes the #2369 sweep).** Two more generated `.kittify/` subtrees
(mission-state repair manifests + quarantine backups; orchestrator per-WP
logs) were neither IGNORED state surfaces nor backfilled, so they dirtied the
tree and failed `accept`'s git-dirty check. Both are now registered IGNORED
local-runtime surfaces (fresh `init` gitignores them) with a sibling
backfill migration for already-initialised projects on upgrade.
- **`saas_client` no longer defaults to a hardcoded `api.spec-kitty.io`; it
fails closed (#2248).** `load_auth_context` fell back to a baked-in SaaS URL
when neither `SPEC_KITTY_SAAS_URL` nor `.kittify/saas-auth.json` supplied one,
risking silently pointing the client at the wrong server. With the #2146
target-authority decision in place, the default is removed and a no-URL
resolution now raises `SaasAuthError` (caught where the no-token path already
was).
- **Sync runtime callers route through the canonical resolved target authority
(#2146).** The tracker client, sharing client, and background sync read the
raw `SyncConfig.get_server_url()` (config-only, hardcoded default) instead of
`resolve_runtime_target().resolved_server_url`, so with `SPEC_KITTY_SAAS_URL`
set, auth/readiness hit the env target while these surfaces silently posted to
the config target (the SC-008 split-brain). All three now resolve the same
target as auth/readiness, and `sync status`/`sync doctor` report the resolved
URL. Separately, `sync server <url>` now accepts loopback HTTP
(`http://localhost`/`127.0.0.1`/`::1`) for the documented local-Docker-SaaS
dev workflow while still requiring HTTPS for remote hosts.
- **compat-planner contract checks are revived (#2419).** Two test surfaces
validated `upgrade`/`render_json` payloads against the committed
`compat-planner.json` contract but silently no-op'd in CI since 2026-04-27
(each resolved the contract via a maintainer-worktree-only path, so it was
`None` and validation was skipped). Both now anchor on the repo root, load
unconditionally, and fail hard on a missing/unreadable contract — which caught
one real drift (a migration description trimmed to satisfy `maxLength`).
- **migration_id contract pattern widened to admit the dotted convention
(#2339).** The `compat-planner.json` `migration_id` pattern
(`^[a-z0-9_]{1,128}$`) rejected 83 of 89 real dotted ids (e.g.
`3.2.0rc45_...`) — a contract-authoring bug that let #2339-class drift slip
through unnoticed. Widened to `^[a-z0-9_.]{1,128}$` (a backward-compatible
widening; no shipped `migration_id` renamed, so persisted per-project ledgers
keep validating).
### ♻️ Changed
- **Internal: version-comparison primitives consolidated into one canonical module (#2417, landing via PR #2414).**
Landing the #2413 fix surfaced three independent "is version A newer than
version B" implementations that had drifted apart in edge-case handling:
`cli/commands/upgrade.py::_version_is_newer` (caught the broad `Exception`
instead of the specific parse failure), `core/upgrade_probe.py::_classify`'s
inline comparison, and the `_upgrade_is_available` helper #2413 had just
added to `session_presence/manager.py`. All three now delegate to a new
pure module, `specify_cli.core.version_compare`
(`try_parse_version` / `is_version_newer`), with no CLI/click/typer
imports so it is safe to import from any layer; the two duplicate boolean
helpers are deleted outright, no aliases left behind. No user-facing
behavior change.
- **Internal: CI-topology census freshness gate is now LOC-insensitive
(#2416).** The gate compared each worklist entry's exact line count and an
LOC-derived sort order, so any PR that shifted the line count of a worklist
directory went red regardless of touching CI routing — a maintenance tax two
PRs paid inside 24h. It now checks membership + committed routing plan only
(an order/LOC-insensitive index at the shared derivation), fixing both the
pytest gate and the `--verify-census` CLI by construction. Zero `src` changes.
- **Internal: test-suite `/tmp` hygiene sweep (#1842, epic #1931).** 97
grandfathered test files were converted off literal `/tmp` paths to
`tmp_path`/sentinels, and the empty-`/tmp` ratchet was flipped to a
self-consistent hard gate so new literal-`/tmp` leaks fail fast. Also fixed a
test-only `--output=` parser leak that could write an invalid Windows filename
(breaking the Windows critical CI job), with a new arch guard blocking
Windows-illegal names and shell-expansion-leak telltales across tracked files
(#2169).
### 💥 Breaking Changes
- **Pre-3.2.x legacy (meta-less) missions are no longer supported for
coordination operations (#2091 / #2462).** A mission with no resolvable
mission identity — no `mission_id`/`mid8` in `meta.json`, the pre-3.2.x
mission-identity model — can no longer drive coordination operations (status
transitions, `move-task`, review/merge coordination writes). Such a mission
now **fails loud** with a customer-actionable error instead of silently
composing a malformed `kitty/mission-<slug>-` ref (the old #2091 defect that
surfaced only as an opaque `git worktree add` exit-128). This intentionally
drops the dual-era legacy-bridge fallback: legacy missions are dwindling and
the dual-support path was a persistent source of split-brain routing bugs.
**Migration:** run `spec-kitty migrate backfill-identity` to mint a ULID
`mission_id` (and derive its `mid8`) for any affected mission — audit first
with `spec-kitty doctor identity`. See
[mission-id-canonical-identity.md](../migrations/mission-id-canonical-identity.md).