From 49486e6fdb09eaa4af03e2a6a36df87ae27858ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Lange?= Date: Wed, 3 Jun 2026 12:40:07 +0200 Subject: [PATCH] chore: complete FIX-G-004 + bump v0.4.0 (5-state trust model release) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the v0.4.0 release commit consolidating since v0.2.0: - 5-state trust model migration (already on main via 62670bd) - TRUST_BADGES + get_trust_badge() helper (PR #2, ef4f770) - test_models.py migrated to v2 vocabulary (PR #2) - datetime.utcnow() cleanup in models.py (this commit, closes FIX-G-004 gap) Changes in this commit: - src/capacium_models/models.py: 6× datetime.utcnow() → datetime.now(timezone.utc). Extended import accordingly. Closes the FIX-G-004 completion gap that the original Stream G codemod missed. - pyproject.toml: version 0.2.0 → 0.4.0 - CHANGELOG.md: v0.4.0 entry documenting breaking changes (TrustState AUDITED removed → PENDING_REVIEW), new features (TRUST_BADGES, get_trust_badge), bug fixes (utcnow cleanup), test migration, and process notes (5-state migration commit landed without PR). Verified locally: PYTHONPATH=src pytest tests/test_models.py → 55 passed, zero utcnow DeprecationWarnings under Python 3.14. Next step (out of scope for this PR): tag v0.4.0 on main + publish to PyPI. capacium-models has no automated release workflow yet — will be handled manually or via a follow-up workflow PR. --- CHANGELOG.md | 63 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/capacium_models/models.py | 14 ++++---- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b19fbf6..d5d62b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,69 @@ All notable changes to this project will be documented in this file. +## capacium-models v0.4.0 — 5-State Trust Model + TRUST_BADGES (2026-06-03) + +### Breaking Changes + +- **Trust model migrated from 4 states to 5 states**: `TrustState` enum is + now `discovered → pending_review → verified → signed → deprecated`. The + `AUDITED` state has been **removed** and renamed to `PENDING_REVIEW`. + Legacy values continue to be mapped via `LEGACY_STATE_MAP` (`"audited"` + → `"pending_review"`). Downstream code referencing `TrustState.AUDITED` + directly must be updated to `TrustState.PENDING_REVIEW`. Database rows + with `trust_state = 'audited'` are normalized transparently by + `normalize_legacy_state()`. + +- **`VALID_TRANSITIONS` updated** to reflect the 5-state graph: + `discovered → pending_review`, `pending_review → {verified, discovered}`, + `verified → {signed, pending_review}`, `signed → {verified, deprecated}`, + `deprecated → pending_review`. The new `DEPRECATED` end-of-life state + supports superseded or abandoned listings. + +### New Features + +- **`TRUST_BADGES`** (`src/capacium_models/trust.py`): single source of + truth dict mapping each v2 state to its display label, symbol, color, + description, and ordering. Eliminates vocabulary drift across surfaces + (CLI, MCP, WP bridge, Exchange) (FIX-F-001). + +- **`get_trust_badge(trust_state: str)`** helper: returns the badge + definition for any trust state, applying legacy normalization on the + way (`audited`/`indexed` → `pending_review` badge; `claimed` → `verified` + badge). Unknown states fall back to `discovered` badge. + +### Bug Fixes + +- **`datetime.utcnow()` removed**: replaced 6 occurrences in + `src/capacium_models/models.py` (lines 74, 165, 215, 289, 326, 352) with + `datetime.now(timezone.utc)`. Closes the FIX-G-004 completion gap that + was missed in the original Stream G codemod (the Fix-PRD claimed + zero `utcnow()` across all 6 repos, but capacium-models was overlooked). + Eliminates `DeprecationWarning` noise under Python 3.12+ and prevents + the future Python release that removes `utcnow()` from breaking the + package. + +- **Migration 0014 trust-state remap docs** (FIX-F-002): the + `migrations/0014_trust_5state.sql` in capacium-exchange now carries a + pre/post distribution comment block documenting the legacy state remap + (`audited → pending_review`, `claimed → verified`). Verified idempotent + and reversible. Tracked in capacium-exchange — included here as the + cross-repo context for this release. + +### Test Migration + +- `tests/test_models.py` migrated from v1 4-state vocabulary to v2 + 5-state vocabulary (19 `TrustState.AUDITED` references → `PENDING_REVIEW`; + 10 string `"audited"` → `"pending_review"`; ordering test length + 4 → 5; method names renamed for clarity). All 55 tests pass. + +### Process Notes + +- The 5-state migration commit (`62670bd`) landed on `main` without a + PR (direct push). This release is the first tag covering those changes; + the migration is captured in this CHANGELOG entry rather than a + separate v0.3.0 to keep the release trail aligned with shipped tags. + ## capacium-models v0.2.0 — Trust Model v2 (2026-05-24) ### Breaking Changes diff --git a/pyproject.toml b/pyproject.toml index 7794772..5c7a6e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "capacium-models" -version = "0.2.0" +version = "0.4.0" description = "Shared domain models for the Capacium ecosystem — Listing, TrustState, TrustMachine, and more" authors = [{name = "Capacium"}] requires-python = ">=3.10" diff --git a/src/capacium_models/models.py b/src/capacium_models/models.py index c29568a..ab94e8e 100644 --- a/src/capacium_models/models.py +++ b/src/capacium_models/models.py @@ -8,7 +8,7 @@ import json import uuid from dataclasses import dataclass, field, asdict -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from typing import Any, Dict, List, Optional @@ -71,7 +71,7 @@ class TrustTransition: def __post_init__(self): if not self.timestamp: - self.timestamp = datetime.utcnow().isoformat() + self.timestamp = datetime.now(timezone.utc).isoformat() def to_dict(self) -> Dict[str, Any]: return asdict(self) @@ -162,7 +162,7 @@ class Listing: def __post_init__(self): if not self.id: self.id = str(uuid.uuid4()) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() if not self.discovered_at: self.discovered_at = now if not self.updated_at: @@ -212,7 +212,7 @@ class Publisher: def __post_init__(self): if not self.id: self.id = str(uuid.uuid4()) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() if not self.created_at: self.created_at = now if not self.updated_at: @@ -286,7 +286,7 @@ class Collection: def __post_init__(self): if not self.id: self.id = str(uuid.uuid4()) - now = datetime.utcnow().isoformat() + now = datetime.now(timezone.utc).isoformat() if not self.created_at: self.created_at = now if not self.updated_at: @@ -323,7 +323,7 @@ def __post_init__(self): if not self.id: self.id = str(uuid.uuid4()) if not self.submitted_at: - self.submitted_at = datetime.utcnow().isoformat() + self.submitted_at = datetime.now(timezone.utc).isoformat() def to_dict(self) -> Dict[str, Any]: return asdict(self) @@ -349,7 +349,7 @@ def __post_init__(self): if not self.id: self.id = str(uuid.uuid4()) if not self.verified_at: - self.verified_at = datetime.utcnow().isoformat() + self.verified_at = datetime.now(timezone.utc).isoformat() def to_dict(self) -> Dict[str, Any]: return asdict(self)