Skip to content

ci(deps): bump the actions group with 5 updates#2

Merged
SimplicityGuy merged 1 commit into
mainfrom
dependabot/github_actions/actions-3f98e791e3
Mar 28, 2026
Merged

ci(deps): bump the actions group with 5 updates#2
SimplicityGuy merged 1 commit into
mainfrom
dependabot/github_actions/actions-3f98e791e3

Conversation

@dependabot
Copy link
Copy Markdown
Contributor

@dependabot dependabot Bot commented on behalf of github Mar 28, 2026

Bumps the actions group with 5 updates:

Package From To
astral-sh/setup-uv 6 7
actions/setup-python 5 6
google/osv-scanner-action 2.3.3 2.3.5
trufflesecurity/trufflehog 3.94.0 3.94.1
codecov/codecov-action 5 6

Updates astral-sh/setup-uv from 6 to 7

Release notes

Sourced from astral-sh/setup-uv's releases.

v7.2.1 🌈 update known checksums up to 0.9.28

Changes

🧰 Maintenance

📚 Documentation

⬆️ Dependency updates

v7.0.0 🌈 node24 and a lot of bugfixes

Changes

This release comes with a load of bug fixes and a speed up. Because of switching from node20 to node24 it is also a breaking change. If you are running on GitHub hosted runners this will just work, if you are using self-hosted runners make sure, that your runners are up to date. If you followed the normal installation instructions your self-hosted runner will keep itself updated.

This release also removes the deprecated input server-url which was used to download uv releases from a different server. The manifest-file input supersedes that functionality by adding a flexible way to define available versions and where they should be downloaded from.

Fixes

  • The action now respects when the environment variable UV_CACHE_DIR is already set and does not overwrite it. It now also finds cache-dir settings in config files if you set them.
  • Some users encountered problems that cache pruning took forever because they had some uv processes running in the background. Starting with uv version 0.8.24 this action uses uv cache prune --ci --force to ignore the running processes
  • If you just want to install uv but not have it available in path, this action now respects UV_NO_MODIFY_PATH
  • Some other actions also set the env var UV_CACHE_DIR. This action can now deal with that but as this could lead to unwanted behavior in some edgecases a warning is now displayed.

Improvements

If you are using minimum version specifiers for the version of uv to install for example

[tool.uv]
required-version = ">=0.8.17"

This action now detects that and directly uses the latest version. Previously it would download all available releases from the uv repo to determine the highest matching candidate for the version specifier, which took much more time.

If you are using other specifiers like 0.8.x this action still needs to download all available releases because the specifier defines an upper bound (not 0.9.0 or later) and "latest" would possibly not satisfy that.

🚨 Breaking changes

... (truncated)

Commits
  • 37802ad Fetch uv from Astral's mirror by default (#809)
  • 9f00d18 chore(deps): bump zizmorcore/zizmor-action from 0.5.0 to 0.5.2 (#808)
  • fd8f376 Switch to ESM for source and test, use CommonJS for dist (#806)
  • f9070de Bump deps (#805)
  • cadb67b chore: update known checksums for 0.10.10 (#804)
  • e06108d Use astral-sh/versions as primary version provider (#802)
  • 0f6ec07 docs: replace copilot instructions with AGENTS.md (#794)
  • 821e5c9 docs: add cross-client dependabot rollup skill (#793)
  • 6ee6290 chore(deps): bump versions (#792)
  • 9f332a1 Add riscv64 architecture support to platform detection (#791)
  • Additional commits viewable in compare view

Updates actions/setup-python from 5 to 6

Release notes

Sourced from actions/setup-python's releases.

v6.0.0

What's Changed

Breaking Changes

Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. See Release Notes

Enhancements:

Bug fixes:

Dependency updates:

New Contributors

Full Changelog: actions/setup-python@v5...v6.0.0

v5.6.0

What's Changed

Full Changelog: actions/setup-python@v5...v5.6.0

v5.5.0

What's Changed

Enhancements:

Bug fixes:

... (truncated)

Commits
  • a309ff8 Bump urllib3 from 2.6.0 to 2.6.3 in /tests/data (#1264)
  • bfe8cc5 Upgrade @​actions dependencies to Node 24 compatible versions (#1259)
  • 4f41a90 Bump urllib3 from 2.5.0 to 2.6.0 in /tests/data (#1253)
  • 83679a8 Bump @​types/node from 24.1.0 to 24.9.1 and update macos-13 to macos-15-intel ...
  • bfc4944 Bump prettier from 3.5.3 to 3.6.2 (#1234)
  • 97aeb3e Bump requests from 2.32.2 to 2.32.4 in /tests/data (#1130)
  • 443da59 Bump actions/publish-action from 0.3.0 to 0.4.0 & Documentation update for pi...
  • cfd55ca graalpy: add graalpy early-access and windows builds (#880)
  • bba65e5 Bump typescript from 5.4.2 to 5.9.3 and update docs/advanced-usage.md (#1094)
  • 18566f8 Improve wording and "fix example" (remove 3.13) on testing against pre-releas...
  • Additional commits viewable in compare view

Updates google/osv-scanner-action from 2.3.3 to 2.3.5

Release notes

Sourced from google/osv-scanner-action's releases.

v2.3.5

This updates OSV-Scanner to v2.3.5.

What's Changed

New Contributors

Full Changelog: google/osv-scanner-action@v2.3.3...v2.3.5

Commits
  • c518547 Merge pull request #124 from google/update-to-v2.3.5
  • 1fc5ec2 Update unified workflow example to point to v2.3.5 reusable workflows
  • 3d5827d Update reusable workflows to point to v2.3.5 actions
  • 7222d1c "Update actions to use v2.3.5 osv-scanner image"
  • a30b4c3 Merge pull request #120 from google/lsc-1771431861.8381045
  • 62f47c7 Fix missing env var after the initial change
  • b7ee968 Refactor Github Action per b/485167538
  • See full diff in compare view

Updates trufflesecurity/trufflehog from 3.94.0 to 3.94.1

Release notes

Sourced from trufflesecurity/trufflehog's releases.

v3.94.1

What's Changed

Full Changelog: trufflesecurity/trufflehog@v3.94.0...v3.94.1

Commits

Updates codecov/codecov-action from 5 to 6

Release notes

Sourced from codecov/codecov-action's releases.

v6.0.0

⚠️ This version introduces support for node24 which make cause breaking changes for systems that do not currently support node24. ⚠️

What's Changed

Full Changelog: codecov/codecov-action@v5.5.4...v6.0.0

v5.5.4

This is a mirror of v5.5.2. v6 will be released which requires node24

What's Changed

Full Changelog: codecov/codecov-action@v5.5.3...v5.5.4

v5.5.3

What's Changed

Full Changelog: codecov/codecov-action@v5.5.2...v5.5.3

v5.5.2

What's Changed

New Contributors

Full Changelog: codecov/codecov-action@v5.5.1...v5.5.2

v5.5.1

What's Changed

... (truncated)

Changelog

Sourced from codecov/codecov-action's changelog.

v5.5.2

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1..v5.5.2

v5.5.1

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.0..v5.5.1

v5.5.0

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.4.3..v5.5.0

v5.4.3

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.4.2..v5.4.3

v5.4.2

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
  • @dependabot ignore <dependency name> minor version will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
  • @dependabot ignore <dependency name> will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
  • @dependabot unignore <dependency name> will remove all of the ignore conditions of the specified dependency
  • @dependabot unignore <dependency name> <ignore condition> will remove the ignore condition of the specified dependency and ignore conditions

Bumps the actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `6` | `7` |
| [actions/setup-python](https://github.com/actions/setup-python) | `5` | `6` |
| [google/osv-scanner-action](https://github.com/google/osv-scanner-action) | `2.3.3` | `2.3.5` |
| [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) | `3.94.0` | `3.94.1` |
| [codecov/codecov-action](https://github.com/codecov/codecov-action) | `5` | `6` |


Updates `astral-sh/setup-uv` from 6 to 7
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@v6...v7)

Updates `actions/setup-python` from 5 to 6
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v5...v6)

Updates `google/osv-scanner-action` from 2.3.3 to 2.3.5
- [Release notes](https://github.com/google/osv-scanner-action/releases)
- [Commits](google/osv-scanner-action@v2.3.3...v2.3.5)

Updates `trufflesecurity/trufflehog` from 3.94.0 to 3.94.1
- [Release notes](https://github.com/trufflesecurity/trufflehog/releases)
- [Commits](trufflesecurity/trufflehog@v3.94.0...v3.94.1)

Updates `codecov/codecov-action` from 5 to 6
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](codecov/codecov-action@v5...v6)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: google/osv-scanner-action
  dependency-version: 2.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: trufflesecurity/trufflehog
  dependency-version: 3.94.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot @github
Copy link
Copy Markdown
Contributor Author

dependabot Bot commented on behalf of github Mar 28, 2026

Labels

The following labels could not be found: ci, dependencies. Please create them before Dependabot can add them to a pull request.

Please fix the above issues or remove invalid values from dependabot.yml.

@SimplicityGuy SimplicityGuy merged commit 9c790be into main Mar 28, 2026
10 of 14 checks passed
@dependabot dependabot Bot deleted the dependabot/github_actions/actions-3f98e791e3 branch March 28, 2026 05:03
SimplicityGuy added a commit that referenced this pull request May 11, 2026
* docs(phase-24): begin schema foundation & agent registry phase

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(24-01): add tests/test_migrations package marker

- Empty __init__.py so pytest discovers the new package
- Pattern mirrors tests/test_models/__init__.py

* test(24-02): add failing test for Agent model

Mirror TagWriteLog test shape (class-based grouping, no fixtures, pure
metadata assertions on the SQLAlchemy declarative layer). All 9 assertions
fail until phaze.models.agent exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(24-02): implement Agent model with slug CHECK constraint

Creates phaze.models.agent.Agent with the post-Phase-24 schema shape:
- id: VARCHAR(64) primary key
- name: VARCHAR(128) not null
- token_hash: VARCHAR(128) nullable (legacy agent has NULL credentials)
- scan_roots: JSONB list[str] with empty-array server default
- last_seen_at / revoked_at: timezone-aware nullable timestamps
- TimestampMixin contributes created_at + updated_at

CheckConstraint name="id_charset" renders as ck_agents_id_charset via the
project naming-convention dict in base.py. The regex literal
'^[a-z0-9]+(-[a-z0-9]+)*$' is intentionally duplicated in this model and
Plan 03's migration 012 (D-14, RESEARCH Pitfall 3) - both must stay
byte-exact to keep app-layer and DB-layer validation in sync.

No relationship() back-refs to FileRecord / ScanBatch (deferred per
CONTEXT.md). All 9 TestAgent assertions now pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(24-02): expand ScanStatus to 4 values and add agents to expected tables

test_scan_status_has_three_values renamed to ..._has_four_values to assert
ScanStatus.LIVE == "live" (lowercase stored value matching existing enum
convention). test_all_tables_defined now expects the agents table in
metadata.tables; both tests fail until Tasks 2-3 complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(24-01): add alembic-driven test DB fixture and step helpers

- tests/test_migrations/conftest.py exports migrated_engine fixture,
  upgrade_to/downgrade_to helpers, _build_alembic_config builder, and the
  MIGRATIONS_TEST_DATABASE_URL constant pointing at a dedicated
  phaze_migrations_test DB (isolation from the parent async_engine fixture).
- The fixture runs alembic.command.upgrade(cfg, "head") + downgrade(cfg,
  "base") instead of Base.metadata.create_all, so real migration revisions
  are validated (RESEARCH Pitfall 2).
- Includes _patched_settings_database_url contextmanager to work around
  alembic/env.py:21 overwriting sqlalchemy.url with settings.database_url;
  patches the singleton's attribute for the upgrade/downgrade duration so
  alembic targets the test DB.

* feat(24-02): add agent_id FKs, LIVE enum value, composite UQ swap

Updates FileRecord and ScanBatch models to the post-Phase-24 schema shape:

scan_batch.py:
- ScanStatus.LIVE = "live" (lowercase value matches existing convention)
- agent_id: String(64) FK to agents.id with ondelete="RESTRICT", nullable=False
- __table_args__ declares ix_scan_batches_agent_id (plain) plus
  uq_scan_batches_agent_id_live (partial unique with predicate
  status = 'live' — must match migration 012 byte-for-byte per D-12)
- New imports: ForeignKey, Index, text (alphabetical isort)

file.py:
- agent_id: String(64) FK to agents.id with ondelete="RESTRICT", nullable=False
- __table_args__ swaps single-column uq_files_original_path for composite
  uq_files_agent_id_original_path; leading column lets Postgres satisfy
  agent_id-only filters without a separate plain index (D-15)

Model represents post-013 truth; nullable=False is the final state even
though the DB column starts nullable in migration 012 (RESEARCH Example 1).

92 model + phase02 tests pass; no regressions across the 140-test scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(24-01): summarize phase 24 plan 01 alembic test substrate

- tests/test_migrations/ package created (empty __init__.py)
- tests/test_migrations/conftest.py exports migrated_engine, upgrade_to,
  downgrade_to, _build_alembic_config, MIGRATIONS_TEST_DATABASE_URL,
  ALEMBIC_INI_PATH
- _patched_settings_database_url contextmanager added (Rule 3 auto-fix)
  to work around alembic/env.py overriding sqlalchemy.url from
  settings.database_url at every run
- Operator pre-condition: phaze_migrations_test DB must exist on localhost
- Covers requirement DATA-04; unblocks 24-02 / 24-03 / 24-04 / 24-05

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(24-02): export Agent from phaze.models barrel

Adds Agent to the import block (alphabetically before AnalysisResult) and
to __all__. alembic/env.py relies on `from phaze.models import *` to seed
Base.metadata for autogenerate; Agent must be in __all__ or migration 012
would silently exclude the new table.

Verifies: `from phaze.models import Agent` works and Base.metadata.tables
now includes "agents" in fresh imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(24-02): complete model-side schema for Agent registry plan

Records the canonical regex literal (^[a-z0-9]+(-[a-z0-9]+)*$) and the
canonical partial-index predicate (status = 'live') that Plan 03's
migration 012 must reuse byte-for-byte. 92 model + phase02 tests pass;
mypy strict clean across 14 source files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(24-03): add migration 012 agents table and legacy backfill

- Create agents table with 8 columns (id, name, token_hash, scan_roots
  JSONB, last_seen_at, revoked_at, created_at, updated_at) and
  ck_agents_id_charset CHECK constraint enforcing slug regex
- Seed legacy-application-server agent born revoked (token_hash NULL,
  revoked_at NOW), with scan_roots resolved from SCAN_PATH env (defaults
  to /data/music). Resolution logged via alembic.runtime.migration logger
- Add nullable agent_id columns to files and scan_batches
- Add FKs fk_files_agent_id_agents and fk_scan_batches_agent_id_agents
  with ON DELETE RESTRICT
- Add plain index ix_scan_batches_agent_id (D-15 - no separate
  ix_files_agent_id; composite UQ in plan 04 covers agent_id lookups)
- Backfill files.agent_id and scan_batches.agent_id WHERE IS NULL to
  the legacy-application-server slug
- Insert legacy agent's LIVE sentinel scan_batch with scan_path '<watcher>'
- Create partial unique index uq_scan_batches_agent_id_live with
  predicate "status = 'live'", AFTER the sentinel INSERT so first run
  cannot violate uniqueness

Regex and partial-index predicate literals match Plan 02 model
declarations byte-for-byte. Downgrade reverses in exact reverse order.

* fix(24-03): run alembic upgrade/downgrade in worker thread

The migrated_engine fixture from plan 24-01 directly calls upgrade_to
inside an async pytest fixture. alembic/env.py invokes
asyncio.run(run_async_migrations()) which raises
RuntimeError("asyncio.run() cannot be called from a running event loop")
because pytest-asyncio (mode=auto) has already started the loop for the
fixture body.

Wrap the sync upgrade_to/downgrade_to helpers in asyncio.to_thread so
the inner asyncio.run runs on a fresh thread with no parent loop.

Discovered while writing plan 24-03 test_012_upgrade.py - the
migrated_engine fixture is unusable without this fix (Rule 3 - blocking
issue inherited from plan 24-01 that did not yet exercise the fixture
at runtime).

* test(24-03): add 13 integration tests for migration 012

Covers VALIDATION.md rows #8-#16 plus the DATA-01 SQL-level CHECK
constraint behavior against a real Postgres instance via the
migrated_engine fixture from plan 24-01:

Fixture-based tests (use migrated_engine):
- test_agents_table_columns - DATA-01 column inventory
- test_scan_roots_is_jsonb - DATA-01 JSONB type
- test_id_charset_check - DATA-01 / T-V5-01 rejects 5 hostile slugs
- test_token_hash_nullable - DATA-01 / T-V6-01 column nullability
- test_legacy_agent_born_revoked - DATA-04 / T-V4-01 audit shape
- test_sentinel_scan_path_literal - DATA-04 '<watcher>' literal
- test_legacy_sentinel_exists - DATA-03 sentinel row count == 1
- test_partial_uq_rejects_dup_live - DATA-03 partial UQ enforcement
- test_partial_uq_allows_multiple_non_live - DATA-03 predicate scope

Self-driving tests (step through revisions 011 -> 012 manually):
- test_legacy_agent_scan_roots_fallback - DATA-04 default /data/music
- test_legacy_agent_scan_roots_from_env - DATA-04 SCAN_PATH override
- test_backfill_files - DATA-04 / T-V5-02 pre-existing files row
- test_backfill_scan_batches - DATA-04 pre-existing scan_batches row

Self-driving tests wrap upgrade_to/downgrade_to in asyncio.to_thread so
alembic's internal asyncio.run does not collide with the pytest-asyncio
event loop, matching the fix applied to the migrated_engine fixture.

Operator pre-condition: phaze_migrations_test DB on localhost:5432.

* docs(24-03): complete migration 012 plan summary

Plan 24-03 shipped:
- alembic/versions/012_add_agents_table_and_backfill.py (additive +
  backfill migration: agents table with slug CHECK, legacy agent
  seeded born-revoked, nullable agent_id columns + FKs with ON DELETE
  RESTRICT, backfill UPDATEs, LIVE sentinel scan_batch, partial UQ
  enforcing one-LIVE-per-agent)
- tests/test_migrations/test_012_upgrade.py (13 integration tests
  covering DATA-01 / DATA-03 / DATA-04 verification rows)
- Rule 3 auto-fix on tests/test_migrations/conftest.py wrapping
  alembic upgrade/downgrade in asyncio.to_thread so the inner
  asyncio.run does not collide with pytest-asyncio's outer loop

Requirements completed: DATA-01, DATA-03, DATA-04.

* feat(24-04): add migration 013 NOT NULL + composite UQ swap

- Tighten agent_id NOT NULL on files and scan_batches (DATA-02 + DATA-03)
- Drop legacy uq_files_original_path single-column unique index
- Create composite uq_files_agent_id_original_path (agent_id leading per D-15)
- Downgrade includes D-16 dupe-detection guard: SELECT ... GROUP BY HAVING
  COUNT(*) > 1 LIMIT 5; raises RuntimeError with 'Cannot downgrade 013->012'
  message and the offending paths before touching any DDL
- Silent dedup is FORBIDDEN per CONTEXT D-16 (irreplaceable music collection)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(24-05): add failing tests for LEGACY_AGENT_ID stamping and composite UQ

- Import Agent model and LEGACY_AGENT_ID constant (not yet defined)
- Update test_bulk_upsert_stores_paths: seed legacy Agent, pass agent_id on ScanBatch and every record
- Update test_bulk_upsert_handles_duplicates: same fixture additions on both records
- Add test_bulk_upsert_same_path_different_agent: two agents, same original_path, composite UQ must allow both rows

Tests currently fail at collection time on ImportError for LEGACY_AGENT_ID (TDD RED).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(24-04): add 5 integration tests for migration 013

Covers VALIDATION.md rows #17-#21:
- DATA-02 files.agent_id is NOT NULL (information_schema check)
- DATA-03 scan_batches.agent_id is NOT NULL (information_schema check)
- DATA-02 SC #2 same original_path under two different agents both succeed
- DATA-02 same (agent_id, original_path) is rejected by composite UQ
- DATA-02 uq_files_original_path dropped; uq_files_agent_id_original_path
  in its place (pg_indexes inventory)

Pattern: uses migrated_engine fixture from plan 24-01 (head=013 after 24-04).
Tests cannot run end-to-end in this sandbox (Postgres not local) - same
documented operator pre-condition as plan 24-03. Static gates pass:
ruff, mypy, pre-commit all green; pytest collects 5/5 tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(24-04): add 3 downgrade tests including D-16 dupe-detection error path

Covers VALIDATION.md rows #22-#24:
- test_downgrade_013_clean: 013->012 on a no-dupe DB restores
  uq_files_original_path and relaxes both agent_id columns to nullable
- test_downgrade_013_fails_on_dupes: D-16 + T-V5-03 mitigation; insert
  two files with same original_path under different agents, attempt
  013->012 downgrade, assert pytest.raises(RuntimeError, match='Cannot
  downgrade 013->012'); verify DB state unchanged (DDL aborted before
  mutation); clean up dupes so finally can downgrade to base
- test_downgrade_012_clean: 012->011 drops agents table, removes both
  agent_id columns, restores uq_files_original_path

Pattern: self-driven upgrade/downgrade sequences (no migrated_engine
fixture). Each test wraps downgrade_to/upgrade_to in asyncio.to_thread
mirroring the conftest fixture pattern from plan 24-01. Cannot run
end-to-end in this sandbox (Postgres not local) - same documented
operator pre-condition as plan 24-03.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(24-04): complete migration 013 plan summary

3 commits: feat be5d60b (migration), test ba944eb (5 upgrade tests),
test fda0b2b (3 downgrade tests including D-16 dupe-detection error path).

All static gates pass: ruff, mypy, pre-commit; 21 tests collected
(13 from 24-03 + 5 + 3). End-to-end test execution and the BLOCKING
operator smoke gate (Task 4) are deferred to operator with running
Postgres + Docker - same documented pre-condition as plan 24-03.

DATA-02 (composite UQ + NOT NULL) and DATA-04 (downgrade safety)
requirements satisfied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(24-05): stamp LEGACY_AGENT_ID and swap to composite conflict target

Ingestion service now writes the Phase 24 placeholder agent attribution on
every newly-discovered FileRecord and ScanBatch, and aligns its upsert
conflict target with the post-013 composite UQ (`agent_id`, `original_path`).

- Add module constant LEGACY_AGENT_ID = "legacy-application-server"
- discover_and_hash_files: add "agent_id": LEGACY_AGENT_ID to every record dict
- bulk_upsert_files: index_elements=["agent_id", "original_path"] (was just original_path)
- run_scan: pass agent_id=LEGACY_AGENT_ID to ScanBatch constructor
- [Rule 1 - Bug] test_discover_files_record_keys: include "agent_id" in expected_keys
  set (the assertion enumerated the old 9-key shape; the new contract has 10 keys)
- [Format - ruff] expected_keys set reflowed across multiple lines (would exceed 150 chars)

All 18 ingestion unit tests pass. Integration tests (3) require operator-provisioned
Postgres on localhost:5432 (same pre-condition Plan 24-03 documented); they collect
and reach the TCP connect step but cannot complete in this sandbox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(24-05): complete ingestion-service plan summary

Phase 24 Plan 05 final summary. Records RED/GREEN commits, the one Rule 1
auto-fix (test_discover_files_record_keys::expected_keys), grep-contract
verification, and the Phase 25 follow-up commitments (remove LEGACY_AGENT_ID,
wire per-request agent attribution from the bearer-token-derived agent_id).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(010): remove duplicate FK constraint declaration on discogs_links

The inline `sa.ForeignKey("tracklist_tracks.id")` on track_id (line 27) and the
explicit `sa.ForeignKeyConstraint(...)` on the same column with name
`fk_discogs_links_track_id_tracklist_tracks` both rendered into the CREATE TABLE,
producing two FK constraints with identical names. Postgres rejected the second
with DuplicateObjectError, blocking `alembic upgrade head` from any fresh DB.

Discovered while running phase 24's deferred operator smoke gate (24-04 Task 4)
against a fresh Postgres container. Already-migrated deployments are unaffected
since they passed revision 010 before the naming-convention collision surfaced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(011): remove duplicate FK constraint declaration on tag_write_log

Same defect class as migration 010 (commit a4a375b): inline
`sa.ForeignKey("files.id")` plus explicit `sa.ForeignKeyConstraint(...)` with
the auto-generated naming-convention name produced two FK constraints with
identical names, blocking fresh `alembic upgrade head` with
DuplicateObjectError on `fk_tag_write_log_file_id_files`.

Discovered while continuing the phase 24 smoke gate after the 010 fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(24-04): record operator smoke-gate PASS with full transcript

Captures the full upgrade/downgrade roundtrip log against a throwaway
postgres:18-alpine container, the SCAN_PATH override smoke confirming
SCAN_PATH=/tmp/test-override flows into agents.scan_roots, and the D-16
RuntimeError downgrade guard firing with exact substring
"Cannot downgrade 013->012" and the collision list
['/test/duplicate.flac'].

Notes the two pre-existing migration defects in 010 and 011 discovered
and fixed during this smoke gate (commits a4a375b and 0a63c61).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(24-01): reset schema in migrated_engine teardown instead of downgrade-to-base

Tests that deliberately insert duplicate original_path rows under different
agents (validating the composite UQ from migration 013) would otherwise trip
the D-16 RuntimeError guard during the fixture's 013->012 downgrade step,
leaving the DB stuck mid-chain. Subsequent tests inherited the bad state.

Replaces `downgrade_to('base')` teardown with a bare DROP SCHEMA public CASCADE
plus CREATE SCHEMA — bypasses the migration chain entirely and is faster than
walking 13 downgrade steps. Also adds a setup-time reset so a test failure in
one run does not strand state for the next.

All 21 migration integration tests (test_012_upgrade, test_013_upgrade,
test_downgrade) now pass when run as a suite against postgres:18-alpine.

Discovered during phase 24 deferred smoke-gate execution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(24): default agent_id to LEGACY_AGENT_ID on FileRecord/ScanBatch + seed in conftest

Phase 24 migration 013 made files.agent_id and scan_batches.agent_id NOT NULL.
Pre-existing tests across test_services, test_routers, and test_tasks construct
FileRecord and ScanBatch instances without setting agent_id and broke en masse
(157 failures against a live DB).

Three changes restore those tests without touching their construction sites:

- Move LEGACY_AGENT_ID from phaze.services.ingestion into phaze.models.agent
  as the canonical definition. Ingestion now imports it.
- Add SQLAlchemy column-level default "legacy-application-server" to both
  FileRecord.agent_id and ScanBatch.agent_id. Production callers in
  phaze.services.ingestion still set it explicitly; the default only fires
  when a caller omits it.
- Seed the legacy Agent row in tests/conftest.py async_engine fixture after
  Base.metadata.create_all so FK constraints from default agent_id resolve.

Aligns with phase 24's locked decision that legacy-application-server is the
universal placeholder until Phase 25 wires real per-agent attribution through
the HTTP API. Removes redundant per-test seeding in test_ingestion.py now that
the root fixture owns it.

Verified against postgres:18-alpine: 790/790 tests pass, mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(phase-24): verification report — PASS with full smoke-gate transcript

DATA-01..04 all VERIFIED against codebase artifacts. Operator smoke gate
executed against postgres:18-alpine and PASSED across all 12 acceptance
criteria. 790/790 tests pass. mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(phase-24): mark phase complete in STATE.md

All 5 plans (24-01..24-05) shipped, verifier PASS, operator smoke gate
against postgres:18-alpine PASSED, 790/790 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(phase-24): ship phase 24 — PR #52

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(24): create phaze_migrations_test DB before running test suite

Phase 24's `tests/test_migrations/` suite connects to a dedicated
`phaze_migrations_test` database (alembic upgrade/downgrade roundtrips need
their own DB so they cannot stomp on tests that use Base.metadata.create_all
against the shared `phaze_test`).

The postgres service container can only auto-create one database via
POSTGRES_DB, so we explicitly CREATE the second one with psql once the
service is healthy. Local dev / deployment targets must do the equivalent
once: CREATE DATABASE phaze_migrations_test OWNER phaze;

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SimplicityGuy added a commit that referenced this pull request May 11, 2026
B1: Plan 04 Task 4 smoke gate — replace `just db-downgrade 011` (the recipe
    takes no arguments) with two successive zero-arg `just db-downgrade` calls
    (013->012, then 012->011). Sequence becomes 1x upgrade, 2x downgrade,
    1x upgrade. Updated step numbering and success criterion accordingly.

B2: RESEARCH.md — rename `## Open Questions` to `## Open Questions (RESOLVED)`
    and add explicit `RESOLVED:` lines on both questions documenting Plan 05's
    in-Phase update path and Plan 02's "no back-references" forbidden pattern.

B3: Test name collision — rename Plan 02 Task 1 Test 7 from
    `test_id_charset_check` to `test_id_charset_constraint_declared` (model-level
    shape assertion). Plan 03 Task 2 keeps `test_id_charset_check` in
    `tests/test_migrations/test_012_upgrade.py` (SQL-level INSERT-rejection).
    VALIDATION.md row #2 now points at the migration test (Wave 2).

W1: CONTEXT.md D-09 / D-12 — corrected `'LIVE'` uppercase to `'live'` lowercase
    in SQL string literals. Added `## Errata` section documenting the fix.

W2: CONTEXT.md D-05 — corrected env var name `PHAZE_SCAN_PATH` to `SCAN_PATH`
    and fallback `/music` to `/data/music` (matches config.py:24 and
    docker-compose.yml:12). Documented in same Errata section.

W3: Plan 02 Task 1 — added note that `Agent` has 8 columns (not ROADMAP SC1's 7)
    because `TimestampMixin` adds `updated_at` per project-wide convention;
    additive, not in conflict with SC1.

W4: Plan 03 Task 1 — fixed self-contradictory grep gate for
    `uq_scan_batches_agent_id_live`: now correctly expects count of 2
    (one create + one drop occurrence in the migration).

Requirements coverage across plans still spans DATA-01..DATA-04.
Wave numbering unchanged. Plans 01 and 05 untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SimplicityGuy added a commit that referenced this pull request May 17, 2026
* docs(29): capture phase context

* docs(state): record phase 29 context session

* docs(29): UI design contract for Agents admin page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29): mark UI-SPEC approved after checker verification

Checker verdict: PASS on 5 dimensions; FLAG on Dimension 5 (spacing) for
two pre-existing project invariants inherited from Phases 27/28
(py-0.5 pill padding, py-3 table cell padding). Both are documented
in the spec's Spacing Exceptions section. Non-blocking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29): research deployment hardening + agents admin

Verified against installed binaries:
- SAQ 0.26.3 + croniter 6.2.2 — 6-field trailing-seconds cron
- httpx 0.28.1 supports verify=<path>
- cryptography NOT a transitive dep — must be added as new runtime dep
- agent_worker is single .py file (not a package) — no refactor needed

* docs(29): add validation strategy

* docs(29): pattern mapping for deployment hardening + agents admin

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29): create phase plan

8 plans across 4 waves covering DIST-01, AUTH-02, AUTH-03, OPS-02, OPS-03, OPS-04. Plan-checker passed after one revision round (3 BLOCKERs + 8 WARNINGs addressed). Decision coverage 23/23.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(29-01): add failing tests for cert_bootstrap + Postgres-free guard

RED phase of Plan 29-01 Task 1. Adds:
- cryptography>=46.0.0,<49 runtime dep (NOT transitive, per RESEARCH
  Critical Discovery #1; verified via uv pip list)
- tests/test_cert_bootstrap.py with 7 LOCKED cases (D-22):
    1. 4-file generation + x509 parseability
    2. idempotency (mtimes unchanged on second call)
    3. banner-via-stdout (capsys) + Pitfall-4 no-secret-leak guard
    4. file modes (0o644 certs, 0o600 keys)
    5. SubjectAlternativeName entries match input
    6. _parse_san_entries DNS vs IP dispatch
    7. WARNING-8 banner-via-logger.warning (caplog) +
       Pitfall-4 no-secret-leak parity for logger channel
- tests/test_task_split.py::test_cert_bootstrap_stays_postgres_free
  (Phase 29 D-22 extension of D-25)

All 7 cert_bootstrap tests + the new task_split case currently fail
with ModuleNotFoundError -- this is the expected RED state.

Refs: D-01, D-02, D-22; AUTH-02

* feat(29-01): implement cert_bootstrap + entrypoint shim

GREEN phase of Plan 29-01 Task 1. Implements:

src/phaze/cert_bootstrap.py
    - ensure_certs_present(certs_dir, cn, sans_csv): idempotent CA + leaf
      generation via cryptography.x509. ECDSA P-256, 10-year CA, 2-year
      leaf. Writes phaze-ca.{crt,key} + phaze-server.{crt,key} with
      0o644 / 0o600 modes.
    - _parse_san_entries(): DNSName for hostnames, IPAddress for IPs
      via ipaddress.ip_address dispatch.
    - _generate_ca / _generate_leaf: x509.CertificateBuilder with
      BasicConstraints + KeyUsage extensions per RESEARCH Pattern 1.
    - Loud banner on actual generation via BOTH print() AND
      logger.warning() (CONTEXT D-02 D-discretion "Both", WARNING-8).
      Banner is a LITERAL CONSTANT referencing only phaze-ca.crt --
      never the private key (Pitfall 4).
    - IMPORT-BOUNDARY INVARIANT: no phaze.database / sqlalchemy.ext.asyncio
      imports (extends Phase 26 D-25; verified by test_task_split).

src/phaze/entrypoint.py
    - main(): reads PHAZE_CERTS_DIR / PHAZE_API_HOST / PHAZE_API_TLS_SANS,
      calls ensure_certs_present, then os.execvp uvicorn with
      --ssl-keyfile / --ssl-certfile flags pointing at the generated
      leaf cert. Process replacement so signals + PID-1 propagate
      cleanly (RESEARCH Pattern 2).
    - Invoked from compose as `uv run python -m phaze.entrypoint`.

All 7 cert_bootstrap tests + the new test_task_split case pass.
ruff + mypy + bandit clean on both new modules.

Refs: D-01, D-02, D-22; AUTH-02

* test(29-01): add TLS integration tests + fix CA chain extensions

RED phase of Plan 29-01 Task 2. Adds:

tests/test_services/test_agent_client_tls.py
    - Real-TLS smoke server fixture (uvicorn in background asyncio task,
      two independent CA bundles in tmp_path/{server,wrong}_certs).
    - test_wrong_ca_raises_connect_error: D-04 success criterion --
      httpx.AsyncClient(verify=wrong_ca) against a server presenting
      the correct cert raises httpx.ConnectError.
    - test_correct_ca_succeeds: same setup with verify=correct_ca
      returns 200 OK.
    - test_construct_agent_client_missing_ca_raises +
      test_construct_agent_client_empty_ca_raises: D-03 fail-fast --
      RuntimeError("CA file empty or unreadable: ...") when the CA path
      is non-existent or zero-byte. Currently RED -- AgentSettings does
      not yet expose agent_ca_file, construct_agent_client does not yet
      validate.

[Rule 1 - Bug] src/phaze/cert_bootstrap.py:
    - Add AuthorityKeyIdentifier + SubjectKeyIdentifier extensions on
      the leaf cert; SubjectKeyIdentifier on the CA cert. Python 3.13's
      ssl module rejects the validation chain with "Missing Authority
      Key Identifier" without these (discovered while running
      test_correct_ca_succeeds against the real cert chain).
    - Add ExtendedKeyUsage(SERVER_AUTH) on the leaf cert; required by
      Python 3.13's strict TLS validation path -- otherwise the leaf
      is rejected when presented to a TLS client expecting a server cert.
    All 7 cert_bootstrap unit tests still pass; the chain now validates
    end-to-end (test_correct_ca_succeeds passes).

Refs: D-03, D-04, D-22; AUTH-02

* feat(29-01): wire verify= through PhazeAgentClient + AgentSettings

GREEN phase of Plan 29-01 Task 2. Implements:

src/phaze/config.py
    - BaseSettings.api_tls_sans (D-02): comma-separated SAN list for the
      auto-generated leaf cert. Default "localhost,127.0.0.1,api" covers
      single-host dev (loopback) + docker-compose service-name DNS.
      Env alias PHAZE_API_TLS_SANS.
    - AgentSettings.agent_ca_file (D-03): path to the operator-distributed
      CA cert. Default "/certs/phaze-ca.crt" matches the in-container
      bind-mount path on the agent side. Env alias PHAZE_AGENT_CA_FILE.

src/phaze/services/agent_client.py
    - PhazeAgentClient.__init__ accepts keyword-only `verify` parameter
      (type: ssl.SSLContext | str | bool, default True). Threaded through
      to httpx.AsyncClient(verify=...). Default True preserves backwards
      compat with all existing respx-based tests (Pitfall 10) -- respx
      mocks below the TLS layer.
    - `ssl` moved to TYPE_CHECKING block (annotation-only usage).

src/phaze/tasks/_shared/agent_bootstrap.py
    - construct_agent_client(cfg) now validates cfg.agent_ca_file at
      construction time: if the path does not exist OR is zero-byte,
      raises RuntimeError("CA file empty or unreadable: ...") so
      misconfiguration surfaces fast (D-03 fail-fast).
    - Passes verify=cfg.agent_ca_file through to PhazeAgentClient.

All 4 TLS integration tests pass. 26 existing respx-based
test_agent_client*.py cases still pass (Pitfall 10 confirmed:
default verify=True preserves the transport-level mock behavior).
Postgres-free import boundary still holds (test_task_split: 5/5).
ruff + mypy clean on all modified modules.

Refs: D-02, D-03, D-04; AUTH-02

* docs(29-01): complete cert-bootstrap + agent-TLS plan

Adds 29-01-SUMMARY.md documenting the 4 plan commits:
- ffdbf5f (test RED): 7 cert_bootstrap cases + task_split extension
- 5840bfe (feat GREEN): cert_bootstrap + entrypoint shim
- 57d9843 (test RED): TLS integration tests + Rule 1 bug fix
  (CA chain extensions: AKI, SKI, EKU(SERVER_AUTH))
- 25c4ca4 (feat GREEN): verify= plumbing through PhazeAgentClient
  + AgentSettings.agent_ca_file + BaseSettings.api_tls_sans

12 net new tests passing. AUTH-02 partially closed (full closure in
Plan 03 once docker-compose api command switches to phaze.entrypoint).

Refs: D-01..D-04, D-22; AUTH-02

* test(29-02): add failing tests for AgentSettings agent_env + redis-password validator

- New tests/test_config/__init__.py marker so pytest discovers the sub-package
- 4 RED cases covering Phase 29 D-06:
  1. agent_env=production + passwordless redis_url -> ValidationError with
     "requires a password in redis_url" substring
  2. agent_env=production + redis://default:<pw>@host:6379/0 constructs OK
  3. agent_env=dev + passwordless redis_url constructs OK (Pitfall 7)
  4. Default agent_env is "dev" when omitted
- Tests pass kwargs directly (cleaner than env-var monkeypatching for model contract)

RED state confirmed: first test fails with "DID NOT RAISE ValidationError"
because the agent_env field + model_validator do not exist yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-02): enforce passworded Redis URL on AgentSettings in production mode

Phase 29 D-06 / AUTH-03 (agent-side half):

- Add `Literal` to typing imports
- Add AgentSettings.agent_env: Literal["dev", "production"] field
  (default "dev"; env alias PHAZE_AGENT_ENV)
- Add AgentSettings._enforce_redis_password_in_production model_validator:
  when agent_env=="production", `urlparse(self.redis_url).password` must be
  set; otherwise raise ValueError("agent_env=production requires a password
  in redis_url (Phase 29 D-06)").
- Field placed adjacent to other PHAZE_AGENT_* fields for grouping.
- model_validator placed after _enforce_required_agent_fields so the
  redis-url check runs after the required-field check.

The pairing server-side hardening (Redis `requirepass` + LAN-bound port)
lands in Plan 03 alongside the docker-compose rewrite; together they fully
close AUTH-03. Dev mode preserves Pitfall 7: fresh clones do `docker compose
up` with no Redis password ceremony.

Verification:
- 4/4 tests in tests/test_config/test_agent_settings_redis_password.py pass
- 22 existing tests in tests/test_config_role_split.py + test_config_worker.py
  pass (no regression)
- uv run mypy src/phaze/config.py: clean
- uv run ruff check + ruff format --check: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29-02): complete agent-side redis password validator plan

Phase 29 Plan 02 SUMMARY. Documents:
- Files created (tests/test_config/__init__.py, tests/test_config/test_agent_settings_redis_password.py)
- File modified (src/phaze/config.py: Literal import + agent_env field + _enforce_redis_password_in_production model_validator)
- 2 commits (4b95029 RED, a7741ff GREEN; no REFACTOR needed)
- 4 new tests; 0 regressions in 22 existing config tests
- D-06 fully implemented; AUTH-03 partial (server-side half lands in Plan 03)
- TDD gate compliance verified; self-check passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(29-03): add failing YAML-parse tests for app-server compose isolation

- tests/test_deployment/__init__.py (pytest sub-package marker)
- tests/test_deployment/test_api_filesystem_isolation.py with 4 tests:
  * test_api_service_has_no_file_mounts (DIST-01)
  * test_controller_worker_has_no_file_mounts (DIST-01)
  * test_no_watcher_or_agent_worker_in_root_compose (D-15 / D-17;
    also asserts audfprint + panako absent)
  * test_redis_hardened (D-05 / AUTH-03; requirepass + LAN bind +
    --no-auth-warning healthcheck)
- All 4 tests FAIL against the current docker-compose.yml — RED step
  of TDD. Task 2 lands the compose rewrite that turns them GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-03): harden app-server compose — strip file mounts, lock down redis

Rewrite root docker-compose.yml as the application-server-only compose
(DIST-01, D-05, D-17, D-19; AUTH-03 server-side). End state: services
block is exactly {api, worker, postgres, redis}.

- api: swap `command:` to `uv run python -m phaze.entrypoint` (Plan 01
  cert-bootstrap shim) and replace the SCAN_PATH mount with a single
  bind volume `${CA_PATH:-./certs}:/certs:rw`.
- worker (controller): drop `MODELS_PATH=/models` from environment and
  remove all three file mounts (SCAN_PATH, MODELS_PATH, OUTPUT_PATH).
  Controller is now fileless.
- DELETE the watcher, agent-worker, audfprint, panako service blocks —
  they live in docker-compose.agent.yml on the file server (Plan 04+).
- DELETE the unused audfprint_data and panako_data named volumes.
- redis: list-form command with `--requirepass ${REDIS_PASSWORD:?...}`
  (fail-fast at compose-parse time); ports bound via
  `${REDIS_BIND_IP:-127.0.0.1}:6379:6379` (loopback default, prod sets
  LAN IP); healthcheck uses
  `redis-cli --no-auth-warning -a ${REDIS_PASSWORD} ping`.

.env.example: add the three Phase-29 variables with comment blocks:
REDIS_PASSWORD=changeme (dev placeholder; Pitfall-7 mitigation),
REDIS_BIND_IP=127.0.0.1, PHAZE_API_TLS_SANS=localhost,127.0.0.1,api.

Dockerfile audit: no MODELS_PATH/SCAN_PATH/OUTPUT_PATH ENV defaults
were present — verify step only, no changes needed.

All 4 tests in tests/test_deployment/test_api_filesystem_isolation.py
now pass (GREEN step of TDD).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29-03): complete app-server compose hardening plan

- 4 YAML-parse structural tests (D-19) in tests/test_deployment/
- docker-compose.yml rewritten: services = {api, worker, postgres, redis}
- api volumes stripped to /certs:rw only; controller worker is fileless
- watcher, agent-worker, audfprint, panako removed (move to agent.yml)
- redis hardened: --requirepass, LAN-bound port, authenticated healthcheck
- .env.example documents REDIS_PASSWORD, REDIS_BIND_IP, PHAZE_API_TLS_SANS
- Dockerfile audited — no MODELS_PATH/SCAN_PATH/OUTPUT_PATH ENV defaults

Closes DIST-01 (app server has no file mounts) and the server-side half
of AUTH-03 (Redis requirepass + LAN binding). Decision IDs D-05, D-17,
D-19 fully implemented; D-15 partial pending Plan 04's agent.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-05): add phaze.scripts.download_models helper + bash shim (D-21)

Extract the essentia model URL list (33 classifier paths + 1 genre model)
from scripts/download-models.sh into a Python helper so both bash and the
agent bootstrap can drive the download from a single source of truth.

- src/phaze/scripts/__init__.py: new package marker
- src/phaze/scripts/download_models.py: download_to(target_dir) public
  entry; _download_one uses .part atomic-rename pattern (T-29-05-03);
  CLI entry via `python -m phaze.scripts.download_models <dir>`
- scripts/download-models.sh: rewritten as a 6-line bash shim that execs
  the Python module (signals + exit code pass through cleanly)
- tests/test_services/test_model_bootstrap.py: scaffold with three
  ensure_models_present cases (RED until Task 2 lands) plus three
  download_to/_download_one cases (GREEN now)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-05): wire ensure_models_present into agent_worker startup (D-21)

Phase 29 D-21 completes OPS-03's auto-download path. A new Postgres-free
shared module owns the .pb-glob + download orchestration; agent_worker
calls it AFTER /whoami succeeds so a bad token / unreachable app server
fails fast in ~60s instead of after a 5-minute 150MB download.

- src/phaze/tasks/_shared/model_bootstrap.py: ensure_models_present
  module (Postgres-free; stdlib + phaze.scripts.download_models only)
- src/phaze/tasks/agent_worker.py: drop the in-place RuntimeError checks;
  call ensure_models_present(Path(cfg.models_path)) as Step 3a after
  whoami_with_retry
- src/phaze/agent_watcher/__main__.py: add WARNING-7 documentation
  comment explaining why the watcher intentionally does NOT auto-download
- tests/test_task_split.py: add test_model_bootstrap_stays_postgres_free
  subprocess case (BLOCKER-1 resolution; parallel to the existing
  agent_bootstrap case)
- tests/test_phase04_gaps.py: replace the two old fail-fast model-dir
  RuntimeError tests with ordering + propagation tests that match the
  new auto-download semantics

Deferred (out of scope, pre-existing from Plan 29-03 compose hardening):
test_docker_compose_has_agent_worker_consuming_agent_queue -- Plan 29-04
moves the agent-worker block into docker-compose.agent.yml, and the
test must be updated to scan both compose files there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29-05): complete models auto-download plan

OPS-03 + D-21 fully implemented. SUMMARY records the 33+1 URL migration
from bash to Python, the WARNING-7 watcher-no-download choice, and the
BLOCKER-1 subprocess import-boundary test (test_model_bootstrap_stays_postgres_free).

deferred-items.md notes the pre-existing
test_docker_compose_has_agent_worker_consuming_agent_queue failure from
Plan 29-03 (compose hardening removed the agent-worker block); the test
will be updated by Plan 29-04 (parallel wave) which lands docker-compose.agent.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(29-07): add failing tests for agent_liveness + humanize (RED)

- tests/test_services/test_agent_liveness.py: 5-state classify matrix
  (12 boundary cases) + sort_key ordering invariants per UI-SPEC.
- tests/test_utils/test_humanize.py: relative_time output table (UI-SPEC
  LOCKED) covering all bucket boundaries, the 89.7s → "89s ago" truncation
  case, and format invariants (no plural-s suffix, single-letter unit).

Both modules ImportError today; GREEN commit follows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-07): implement agent liveness classifier + humanize helper (GREEN)

Wave 0 of plan 29-07 (OPS-04 UI half — pure-function tier):

- src/phaze/constants.py: add AGENT_LIVENESS_ALIVE_SECONDS=90 +
  AGENT_LIVENESS_STALE_SECONDS=300 (Phase 29 D-12 LOCKED thresholds).
- src/phaze/services/agent_liveness.py: pure-function classify(agent, now)
  → AgentStatus literal in {alive, stale, dead, revoked, never} with
  precedence revoked → never → alive/stale/dead. sort_key returns
  (revoked_int, status_rank, neg_last_seen) so revoked agents land last,
  non-revoked sort alive→stale→dead→never, ties break by last_seen DESC.
- src/phaze/utils/__init__.py + src/phaze/utils/humanize.py: relative_time
  helper producing "never" / "just now" / "Ns ago" / "Nm ago" / "Nh ago" /
  "Nd ago" with int-truncate semantics (UI-SPEC LOCKED bucket table).

Reconciled UI-SPEC documentation defect (line 248 prose example "89.7s →
89s ago" is inconsistent with its own bucket table lines 232-241; the
table is authoritative — see test docstring for Rule-1 fix rationale).

All 51 tests pass; mypy + ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(29-07): add failing tests for /admin/agents router (RED)

Wave 1 RED gate for plan 29-07. 10 tests covering:
- Full page render with base.html chrome
- HX-Request: true returns partial only
- Dedicated /_table partial route (always partial, never halts polling)
- 5-state status pill rendering with LOCKED Tailwind classes
- Empty state UI-SPEC §Empty State LOCKED copy
- Sort order alive → stale → dead → never → revoked
- 3 BLOCKER-2 tests: htmx event listener + role=alert failure footer +
  localStorage `phaze:agents:lastError` plumbing
- Production-wiring smoke (router registered in main.create_app)

Currently fails at import — phaze.routers.admin_agents does not exist yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-07): admin/agents router + templates + nav link (GREEN)

Wave 1 of plan 29-07 (OPS-04 UI half — Wave-1 deliverables):

- src/phaze/routers/admin_agents.py: APIRouter(prefix="/admin/agents"); two
  handlers — `page` (HX-Request-aware, full page OR partial) and
  `table_partial` (always partial, the canonical 5s polling target).
  `_load_agents` injects transient `agent._status` via classify(a, now) and
  sorts via sort_key (Phase 27 transient-attr pattern). No
  get_authenticated_agent dep (operator-facing on private LAN, consistent
  with pipeline.py / pipeline_scans.py precedent).

- src/phaze/templates/admin/agents.html: page shell extending base.html;
  current_page="admin_agents"; hosts the MANDATORY htmx:responseError +
  htmx:sendError + htmx:afterSwap listener writing/clearing
  `phaze:agents:lastError` localStorage (BLOCKER-2 UI-SPEC §Error /
  Failure-Tolerant Refresh LOCKED).

- src/phaze/templates/admin/partials/agents_table.html: HTMX
  self-replacing <section> (hx-get/hx-trigger/hx-swap=outerHTML, never
  halts — UI-SPEC §Polling LOCKED). Empty state + 6-column table + happy-
  path "Last refreshed Ns ago" Alpine footer + MANDATORY red role=alert
  "Refresh failed at HH:MM:SS" footer driven by localStorage (BLOCKER-2).

- src/phaze/templates/admin/partials/_status_pill.html: 5-state liveness
  pill with LOCKED Tailwind palette (alive=green-100/950,
  stale=amber-100/950, dead=red-100/950, revoked/never=gray-100/800) +
  redundant aria-label="Status: <state>" for screen readers.

- src/phaze/templates/base.html: new "Agents" nav link inserted between
  Audit Log and the theme toggle. Uses short-slug
  `current_page == 'admin_agents'` per WARNING-1 (matches live convention
  where Audit Log uses 'audit' not 'audit_log'). aria-current="page" is
  a forward-looking a11y upgrade applied only to this new link.

- src/phaze/main.py: register admin_agents.router alongside Phase 27/28
  routers.

All BLOCKER-2 grep gates pass:
  agents.html: htmx:responseError × 2, htmx:sendError × 2, htmx:afterSwap × 2,
              phaze:agents:lastError × 2, localStorage.setItem × 1,
              localStorage.removeItem × 1.
  agents_table.html: localStorage.getItem × 2, phaze:agents:lastError × 4,
                     "Refresh failed" × 2, role="alert" × 1.

Test status:
- test_router_registered_in_main_app passes (non-DB, structural).
- 9 DB-backed tests collect cleanly; cannot execute locally (no Postgres
  on the executor host). Logic verified via direct Jinja render smoke
  (positions, pill classes, BLOCKER-2 markup, relative_time output).
- mypy + ruff clean on all new files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29-07): complete admin/agents UI plan

Plan 07 of Phase 29 (OPS-04 UI half) complete. Summary documents:

- Closes OPS-04 (combined with Plan 06 heartbeat caller half: full OPS-04
  closure).
- D-11..D-14 LOCKED contracts implemented.
- BLOCKER-2 (UI-SPEC §Error / Failure-Tolerant Refresh, status: approved)
  DELIVERED in v1 — htmx event listener + localStorage `phaze:agents:
  lastError` + red role=alert banner all shipped; 3 dedicated tests +
  10 grep gates verify presence.
- WARNING-1 RESOLVED: short-slug `current_page == 'admin_agents'` nav
  convention adopted (matches live base.html where Audit Log uses
  'audit', not 'audit_log').
- 1 deviation auto-fixed (Rule 1): UI-SPEC line 248 prose example
  "89.7s → 89s ago" is internally inconsistent with its own bucket
  table; truncation rule verified with non-conflicting 59.7s test
  instead. Bucket table is the authoritative spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(29-w1): update test_shared_agent_bootstrap for D-03 CA-file gate

Plan 29-01 added a CA-file fail-fast in construct_agent_client. Two pre-
existing tests in tests/test_tasks/test_shared_agent_bootstrap.py called
the function without providing PHAZE_AGENT_CA_FILE and broke once Wave 1
merged. Use cert_bootstrap.ensure_certs_present in the test fixture to
generate a real CA that httpx can load via verify=path.

Caught by the post-merge test gate after Wave 1 of phase 29.

* docs(phase-29): update tracking after wave 1

* test(29-04): add failing YAML-parse tests for docker-compose.agent.yml (RED)

Four LOCKED structural assertions for the file-server compose:
  1. test_agent_compose_service_list — exactly {worker, watcher, audfprint, panako}
  2. test_agent_compose_has_no_postgres_env — DIST-04 invariant
  3. test_worker_service_has_phaze_role_agent — D-17
  4. test_all_scan_path_mounts_use_failfast_syntax — WARNING-3

All four currently fail because docker-compose.agent.yml does not yet
exist. The GREEN commit will create the compose file + env template.

Per WARNING-3: the fail-fast regex test rejects future drift to
${SCAN_PATH:-/data/music} loose-default form that would let
docker compose up succeed on a misconfigured file-server host.

Refs phase 29-04 plan, D-15..D-17, D-22 test surface.

* test(29-06): add failing tests for SAQ heartbeat cron handler

- 4 happy-path tests in test_heartbeat_cron.py:
  * success: heartbeat_tick POSTs HeartbeatRequest with correct payload
  * ctx-missing: missing api_client/agent_identity -> WARNING + return
  * queue.info-fail: queue_depth defaults to 0; heartbeat still POSTs
  * importlib metadata: agent_version sourced from importlib.metadata
- 1 failure test in test_heartbeat_failure.py:
  * AgentApiServerError -> WARNING logged; no exception escapes (D-09)
- Tests use ctx["worker"].queue (NOT ctx["queue"]) per RESEARCH Pitfall 8
- AgentApiServerError constructed positional-only (no status_code= kwarg)
- RED step: tests FAIL with ModuleNotFoundError until heartbeat.py lands

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(29-04): create docker-compose.agent.yml + .env.example.agent (GREEN)

The file-server-host compose file (D-15) declares exactly 4 services:
worker, watcher, audfprint, panako. Worker + watcher pull from
ghcr.io/simplicityguy/phaze (D-16 with `${PHAZE_IMAGE_TAG:-latest}`);
sidecars retain `build:` (not yet on GHCR per D-15).

All 4 services use `${SCAN_PATH:?SCAN_PATH required}` fail-fast
interpolation (WARNING-2 unified explicit-message form; WARNING-3
test enforces). MODELS_PATH bind mount is rw on worker + watcher for
D-21 auto-download; CA_PATH bind mount is ro everywhere.

Adds `.env.example.agent` with every variable a file-server host
needs (D-23 portion): PHAZE_IMAGE_TAG, PHAZE_AGENT_API_URL,
PHAZE_REDIS_URL, PHAZE_AGENT_{ID,TOKEN,QUEUE}, PHAZE_AGENT_CA_FILE,
PHAZE_AGENT_ENV, SCAN_PATH, MODELS_PATH, CA_PATH,
PHAZE_AGENT_SCAN_ROOTS. Production-pin guidance lives in inline
comments.

Also resolves the Plan 29-05 deferred test failure: updates
`tests/test_phase04_gaps.py::test_docker_compose_has_agent_worker_consuming_agent_queue`
to scan BOTH `docker-compose.yml` and `docker-compose.agent.yml`. The
agent-worker now lives in `docker-compose.agent.yml::worker`, so the
Phase 27 UAT gap-13 invariant is again codified across the split
compose surface. Marks the deferred-items.md entry resolved.

All 4 RED tests now pass.

Refs phase 29-04 plan, D-15, D-16, D-17, D-22 (agent-compose portion),
D-23 (.env.example.agent portion).

* test(29-04): add failing workflow-tag check (WARNING-4 RED)

Adds test_docker_publish_workflow_tags_both_latest_and_version — a 5th
test in test_agent_compose.py. Replaces the original checkpoint:human-verify
task with an automated YAML-parse check that .github/workflows/docker-publish.yml
emits BOTH a `:latest` tag and a `:v<version>` tag (D-16).

Currently fails because docker-publish.yml's docker/metadata-action step
only declares `type=raw,value=latest`, `type=ref,event=branch`, `type=ref,event=pr`,
and `type=schedule,pattern=...` — no `type=semver,pattern={{version}}` and
no `type=ref,event=tag`. The GREEN commit will extend the workflow.

The plan stays autonomous (no human checkpoint); the regression-detection
is now in CI permanently.

Refs phase 29-04 plan WARNING-4 resolution.

* feat(29-04): extend docker-publish.yml tag strategy + realign api URL (GREEN)

Two coupled changes to satisfy the WARNING-4 automated test and align
the published api image URL with docker-compose.agent.yml:

1. Tag strategy (D-16 + WARNING-4): the docker/metadata-action step now
   emits `type=raw,value=latest`, `type=semver,pattern={{version}}`,
   `type=semver,pattern={{major}}.{{minor}}`, `type=ref,event=tag`,
   `type=ref,event=branch`, `type=ref,event=pr`, and the schedule tag.
   On a tagged release `v4.0.0`, this produces `:latest`, `:v4.0.0` (via
   ref,event=tag), `:4.0.0` and `:4.0` (via semver). Operators get the
   full set of stability rungs the .env.example.agent comments
   reference (PHAZE_IMAGE_TAG=v4.0.0 production pin).

2. Image URL realignment (D-15): the matrix entry for `api` now sets
   `image_suffix: ""`, pushing to the BARE-repo URL
   `ghcr.io/simplicityguy/phaze:<tag>` — the exact URL
   docker-compose.agent.yml's worker + watcher pull from. The sidecars
   keep their `/audfprint` and `/panako` sub-paths because agent.yml
   builds them locally (D-15) and does not pull them from GHCR.

This makes `test_docker_publish_workflow_tags_both_latest_and_version`
pass, closing WARNING-4 with an autonomous test instead of a
checkpoint:human-verify task.

Verification result: `fixed` + `url-realigned` (both the tag pattern
and the image URL needed adjustment).

Refs phase 29-04 plan WARNING-4 resolution.

* docs(29-04): complete docker-compose.agent.yml + GHCR-tag verification plan

OPS-02 fully closed. Lands the file-server-host compose surface
(docker-compose.agent.yml + .env.example.agent) with exactly 4
services and replaces the original GHCR-tag human-verify checkpoint
with an automated YAML-parse test (WARNING-4 resolution). Workflow
extended to emit :v<version> tags and api image realigned to the
bare-repo URL ghcr.io/simplicityguy/phaze.

Also resolves the Plan 29-05 deferred test (gap-13 invariant now
codified across the split compose surface).

Plan stays autonomous: true. 5 new tests, all green, no regressions
across deployment + adjacent suites (22 tests).

* feat(29-06): wire SAQ 30s heartbeat cron handler (OPS-04 caller)

Land the agent-side half of OPS-04 (D-07..D-10):

- New src/phaze/tasks/heartbeat.py with heartbeat_tick(ctx) async cron
  handler. Reads ctx["api_client"], ctx["agent_identity"],
  ctx["worker"].queue (NOT ctx["queue"] per RESEARCH Pitfall 8); builds
  HeartbeatRequest(agent_version=importlib.metadata.version("phaze"),
  worker_pid=os.getpid(), queue_depth=Queue.info()["queued"]) and POSTs
  it via PhazeAgentClient.heartbeat.
- Defensive: ctx not initialized -> WARNING + return; queue.info()
  failure -> default queue_depth=0 + still POST; AgentApiError -> WARNING
  + swallow (D-09 fire-and-forget; SAQ retries on next tick).
- agent_worker.py: import CronJob + heartbeat_tick; add heartbeat_tick
  to settings.functions; add cron_jobs=[CronJob(heartbeat_tick,
  cron="* * * * * */30", unique=True, timeout=10)]. Trailing-seconds
  6-field form per RESEARCH Critical Discovery #2 (the CONTEXT.md D-08
  leading-seconds example would fire every second; verified empirically
  with croniter -- gaps are 30s vs 1s).
- agent_worker.py stays a single .py file (Pitfall 9 avoided).
- heartbeat.py is Postgres-free (banner documents the invariant).

Tests: all 5 heartbeat tests pass (GREEN); test_task_split still passes;
tests/test_tasks/ suite passes (excluding the pre-existing Plan 29-03/04
deferred test_docker_compose_has_agent_worker_consuming_agent_queue).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29-06): summary for OPS-04 heartbeat caller plan

Captures Task 1 (RED tests) + Task 2 (GREEN implementation) outcomes,
threat-model mitigations, and the one notable RESEARCH Critical
Discovery #2 fact: the cron string is the trailing-seconds 6-field form
`* * * * * */30` (NOT the leading-seconds form from CONTEXT.md D-08).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(phase-29): update tracking after wave 2

* docs(29-08): justfile recipes, deployment.md, PROJECT.md, update-project.sh (Task 1)

- justfile: add up-agent + up-all recipes under [group('dev')]; existing `up` unchanged
- docs/deployment.md (new, 230 lines): 6-step two-host operator walkthrough,
  D-20 filesystem-isolation smoke, CA rotation guidance, production checklist;
  required strings present: phaze-ca.crt (9), just up-agent (5), REDIS_PASSWORD (3),
  /admin/agents (2), PHAZE_AGENT_TOKEN (2)
- .planning/PROJECT.md: new "### Deployment (v4.0 — Distributed Agents)" subsection
  under Constraints; documents two-compose-file invariant, HTTPS internal CA,
  Redis password-bound LAN, zero-new-pip-deps beyond cryptography
- scripts/update-project.sh: audited (pure dependency/version orchestrator with no
  Python module enumeration); left untouched per plan rule

Closes D-18 (justfile recipes), D-20 (filesystem-isolation smoke documented),
D-23 (operator workflow + doc sweep).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(29-08): close Task 2 with verified-docs-only signal (SUMMARY)

Operator reviewed docs/deployment.md end-to-end against the live codebase
and confirmed all commands, env vars, routes (/admin/agents,
/api/internal/agent/heartbeat), and paths (/data/music, /certs/phaze-ca.crt)
match the compose mounts and router prefixes. Cert-bootstrap banner text
matches src/phaze/cert_bootstrap.py verbatim.

Resume signal: verified-docs-only (Option C from the checkpoint).

Follow-up: real-deployment smoke deferred until file-server hardware is
available — tracked as a v4.0 outstanding UAT item in the SUMMARY's
"Outstanding Items" section. Structural CI tests under tests/test_deployment/
cover compose-file invariants in the meantime.

Plan 29-08 complete — Phase 29 (deployment-hardening-agents-admin) closed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(phase-29): update tracking after wave 3

* test(29): persist human verification items as UAT

* fix(29-cr-01,cr-02): production https:// guard + bind PHAZE_REDIS_URL env-var

Two critical issues from gsd-code-reviewer's REVIEW.md, both in
src/phaze/config.py:

CR-01: .env.example.agent:22 references AgentSettings._enforce_https_in_production
as a guard that refuses http:// agent_api_url in production. The guard did
not exist — operators following the docs could ship plaintext bearer tokens.
Adds the validator (and three test cases covering https-ok / http-blocked /
dev-permits-http).

CR-02: BaseSettings.redis_url and database_url had no validation_alias, so
pydantic-settings only accepted bare REDIS_URL / DATABASE_URL env-var names.
Operators using PHAZE_REDIS_URL (as documented in .env.example.agent) hit
the default passwordless URL and tripped _enforce_redis_password_in_production
with a misleading "requires a password" error — preventing the production
agent from starting. Adds AliasChoices on both fields and an env-var
integration test that exercises the binding via AgentSettings() with no
kwargs (the 29-02 tests pass kwargs directly and never hit the env path).

* fix(29-cr-03): model_bootstrap rejects partial-download state

ensure_models_present previously short-circuited whenever *any* .pb file
existed in the models directory. An interrupted first download (e.g., 1/34
files written before SIGTERM) permanently left every subsequent agent
start skipping re-download — the agent would silently break at analysis
time when essentia tried to load the 2-33 missing weights.

Compares the observed .pb count against
len(CLASSIFIER_MODELS) + len(GENRE_MODELS). Partial state logs WARNING
with the observed/expected counts and re-invokes download_to, which is
idempotent at the per-file level (_download_one skips existing dests).

Two test updates:
- tests/test_services/test_model_bootstrap.py: populated-no-op test now
  writes all 34 expected files; new partial-triggers-redownload test
  pins the WARNING path.
- tests/test_tasks/test_agent_startup_banner.py: two pre-existing tests
  patched pathlib.Path.glob to return a single fake .pb and relied on
  the old loose check. Patch ensure_models_present directly so the
  banner / queue-mismatch logic under test is not coupled to the
  completeness rule.

Caught by the gsd-code-reviewer agent (Phase 29 REVIEW.md CR-03).

* docs(phase-29): complete phase execution

* docs(milestone-v4.0): audit report — passed with documentation drift

3-source requirements cross-reference: all 26 requirements satisfied
in code (verification + summary + wiring). 22/22 cross-phase exports
wired, 12/12 internal API routes consumed, all 5 E2E flows traced.

Documentation drift surfaced (does not affect runtime):
- 13 REQUIREMENTS.md traceability entries stale at Pending despite
  verified-passed phases (DIST-04/05, DATA-01..04, AUTH-01/04,
  TASK-04, EXEC-01..04)
- ROADMAP.md Phase 24 checkbox still [ ]
- Phase 24 VERIFICATION.md filename unprefixed

Tech debt carried into post-milestone backlog: P28-WR-03, P28-RACE-01,
P29-WR-01..04, P29-IN-01..03 (all advisory; none block archive).

* docs(milestone-v4.0): close documentation drift surfaced by audit

1. REQUIREMENTS.md: 13 stale `[ ]` checkboxes → `[x]` and 13 `| Pending |`
   traceability rows → `| Complete |` (DIST-04, DIST-05, DATA-01..04,
   AUTH-01, AUTH-04, TASK-04, EXEC-01..04). Traceability table footer
   bumped to today with the v4.0 completion note. The integration
   checker confirmed all 26 requirements are wired in code; the rows
   were already satisfied — the doc just hadn't been touched since
   2026-05-11.

2. ROADMAP.md: flip Phase 24 checkbox `[ ]` → `[x] (completed 2026-05-11)`.
   Phase 24 VERIFICATION.md `status: passed, score 4/4` since 2026-05-11.

3. Rename `phases/24-schema-foundation-agent-registry/VERIFICATION.md`
   → `24-VERIFICATION.md` to match the v4.0 convention used by every
   other phase (`{phase_num}-VERIFICATION.md`). The unprefixed name
   broke `gsd-sdk query find-phase` discovery.

* docs(phase-29): add REVIEW.md from gsd-code-reviewer audit

* chore: archive v4.0 milestone files

Snapshot v4.0 Distributed Agents milestone:
- .planning/milestones/v4.0-ROADMAP.md (full phase details)
- .planning/milestones/v4.0-REQUIREMENTS.md (26/26 satisfied)
- .planning/milestones/v4.0-MILESTONE-AUDIT.md (moved from .planning/)
- MILESTONES.md prepended with v4.0 entry + delivered summary
- PROJECT.md full evolution review (Current State, Validated, Key Decisions outcomes)
- STATE.md status -> milestone_complete; v4.0 velocity recorded
- ROADMAP.md collapses v4.0 Phases 24-29 into <details>

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: remove REQUIREMENTS.md for v4.0 milestone close

Archived to .planning/milestones/v4.0-REQUIREMENTS.md (all 26 reqs
satisfied). Next milestone will define fresh requirements via
/gsd:new-milestone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: update retrospective for v4.0 milestone

Append v4.0 Distributed Agents section: what was built, what worked,
what was inefficient, patterns established (settings split factory,
subprocess import-boundary tests, 403-before-state-machine guard,
pre-uvicorn entrypoint shim, etc.), key lessons (8), cost
observations.

Update Cross-Milestone Trends: process evolution row for v3.0 + v4.0,
cumulative quality table, top lessons (7) verified across milestones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): unbreak Phase 29 CI — 3 root causes

1. **`admin_agents` router 422s on all 9 tests + production** —
   `AsyncSession` was imported under `if TYPE_CHECKING:` with
   `from __future__ import annotations`, so FastAPI's `get_type_hints`
   could not resolve the runtime annotation in
   `Annotated[AsyncSession, Depends(get_session)]` and treated
   `session` as a query parameter (`422 {"detail":[{"type":"missing","loc":["query","session"]}]}`).
   Move `from sqlalchemy.ext.asyncio import AsyncSession` to a
   runtime import (matches `agent_files.py` pattern, noqa TC002 since
   FastAPI requires runtime resolution). Fixes all 9
   `test_admin_agents.py` failures and the production route.

2. **`test_settings_redis_url_default` env leak** — Phase 29-02 added
   `PHAZE_REDIS_URL` as a pydantic `AliasChoices` alias for the
   `redis_url` field, and CI sets `PHAZE_REDIS_URL=redis://localhost:6379/0`
   for the test Redis service. The default-value test only deleted
   `REDIS_URL`, not the new alias. Delete all three spellings
   (`PHAZE_REDIS_URL`, `REDIS_URL`, `redis_url`) before asserting on
   the default.

3. **`validate-docker-compose` job parse-fail** — Phase 29-03 added
   `${REDIS_PASSWORD:?REDIS_PASSWORD required}` to the application-server
   `docker-compose.yml`, and Phase 29-04 added `${SCAN_PATH:?...}` on
   four services in the new `docker-compose.agent.yml`. The CI job
   only did `touch .env`, so compose-parse fail-fast tripped before
   anything could be validated. Supply placeholders for both compose
   files and validate the new agent compose alongside the existing
   app-server compose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(29): close codecov patch-coverage gaps on PR #63

Codecov flagged 26 lines missing across 3 Phase 29 modules
(patch coverage 90.33%). This commit takes all three files to 100%.

- tests/test_entrypoint.py (new, 14 lines covered)
  Monkeypatches ensure_certs_present + os.execvp; verifies
  env-var defaults, env-var overrides, and the ensure→execvp
  sequencing invariant (RESEARCH Pattern 2: cert files must
  exist before uvicorn boots against --ssl-keyfile/--ssl-certfile).

- tests/test_scripts/test_download_models.py (new, 10 lines covered)
  respx-mocked tests for _download_one (idempotent skip on existing
  dest, atomic .part-then-rename on success, 4xx leaves dest absent
  so model_bootstrap's *.pb glob retries) and download_to (walks
  CLASSIFIER_MODELS + GENRE_MODELS, no-ops on a populated dir).

- tests/test_cert_bootstrap.py — added test_unparseable_existing_
  certs_trigger_regeneration to cover lines 202-203 (the WARNING
  + regeneration branch when all 4 files exist but parse as garbage).

- Tagged the two `if __name__ == "__main__":` CLI invocation guards
  in entrypoint.py and download_models.py with `# pragma: no cover`
  so coverage reflects what is reachable from `python -m`.

Coverage after this commit:
  cert_bootstrap.py:    96.77% -> 100.00%
  entrypoint.py:         0.00% -> 100.00%
  download_models.py:   67.74% -> 100.00%

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant