docs: record migration head 0020 in the release manifest and docs

RELEASE_MANIFEST.json still said head 0011 (repo was at 0018, now 0020)
and RELEASE_CANDIDATE_V1.md said 0010. Updated both, plus
AI_PROJECT_STATE.md and PILOT_DEPLOYMENT.md, to head
0020_not_null_timestamps as applied in production.

scripts/release_manifest.py reported the 4-digit file prefix as the head
("0020" instead of "0020_not_null_timestamps") and always read the
applied revision from the dev container. It now asks Alembic for the real
head and revision count, and the Hub container is configurable
(HUB_CONTAINER); the source is recorded in the manifest.

tests/test_release_candidate.py now fails when the manifest head differs
from Alembic's head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-23 07:23:13 -03:00
co-authored by Claude Opus 5.5
parent 6bcfac6497
commit d33149f5ee
6 changed files with 52 additions and 9 deletions
+13 -4
View File
@@ -6,6 +6,7 @@ Registers together: release version, agent/hub versions, fiscal engine + decisio
(kind, provider, sha256, record_count) read from the running Hub, alembic head (from the migration files) and the applied revision.
"""
import json
import os
import re
import subprocess
import sys
@@ -31,10 +32,18 @@ def main():
from central.app.icms_context import ICMS_CONTEXT_ENGINE_VERSION
from central.app.product_defaults import PRODUCT_DEFAULTS_ENGINE_VERSION
version = (ROOT / "release" / "VERSION").read_text(encoding="utf-8").strip()
heads = sorted(p.name.split("_")[0] for p in (ROOT / "central" / "alembic" / "versions").glob("00*.py"))
# Real revision ids (e.g. "0020_not_null_timestamps"), not file-name prefixes:
# from 0015 on the id is longer than the 4-digit prefix.
from alembic.config import Config
from alembic.script import ScriptDirectory
alembic_cfg = Config(str(ROOT / "central" / "alembic.ini"))
alembic_cfg.set_main_option("script_location", str(ROOT / "central" / "alembic"))
script = ScriptDirectory.from_config(alembic_cfg)
head, revision_count = script.get_current_head(), len(list(script.walk_revisions()))
container = os.getenv("HUB_CONTAINER", "projeto-fiscal-hub-1")
try:
applied = subprocess.run(["docker", "exec", "projeto-fiscal-hub-1", "sh", "-c", "cd /app && alembic -c central/alembic.ini current"], capture_output=True, text=True, timeout=60).stdout
applied = re.findall(r"^(\d{4})", applied, re.M)[-1:] or ["unknown"]
current = subprocess.run(["docker", "exec", container, "sh", "-c", "cd /app && alembic -c central/alembic.ini current"], capture_output=True, text=True, timeout=60).stdout
applied = re.findall(r"^(\d{4}\S*)", current, re.M)[-1:] or ["unknown"]
except Exception: # noqa: BLE001
applied = ["unknown"]
datasets = sorted(({"kind": d["kind"], "sha256": d["sha256"][:16], "record_count": d["record_count"]} for d in get(f"{hub}/v1/datasets/status", token) if d["status"] == "ACTIVE"), key=lambda x: x["kind"])
@@ -42,7 +51,7 @@ def main():
"hub": get(f"{hub}/version", token), "agent_version": version,
"fiscal_engine": {"ENGINE_VERSION": ENGINE_VERSION, "DECISION_VERSION": DECISION_VERSION, "DECISION_ENGINE_VERSION": DECISION_ENGINE_VERSION,
"PRODUCT_DEFAULTS_ENGINE_VERSION": PRODUCT_DEFAULTS_ENGINE_VERSION, "ICMS_CONTEXT_ENGINE_VERSION": ICMS_CONTEXT_ENGINE_VERSION},
"migrations": {"alembic_head_in_repo": heads[-1], "applied_revision": applied[0], "count": len(heads)},
"migrations": {"alembic_head_in_repo": head, "applied_revision": applied[0], "applied_revision_source": f"container {container}", "count": revision_count},
"datasets_active": datasets, "note": "A release bump does not change fiscal semantics; engine/decision versions are versioned independently."}
(ROOT / "release" / "RELEASE_MANIFEST.json").write_text(json.dumps(manifest, indent=1), encoding="utf-8")
print(json.dumps({"version": version, "engine": ENGINE_VERSION, "migrations": manifest["migrations"], "datasets": len(datasets)}))