semantics

This commit is contained in:
2026-03-27 21:27:31 +03:00
parent 7c85552132
commit 2ed66bfebc
182 changed files with 21186 additions and 10254 deletions

View File

@@ -1,12 +1,12 @@
# [DEF:publication_service:Module]
# [DEF:PublicationService:Module]
# @COMPLEXITY: 5
# @SEMANTICS: clean-release, publication, revoke, gate, lifecycle
# @PURPOSE: Enforce publication and revocation gates with append-only publication records.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.approval_service
# @RELATION: DEPENDS_ON -> backend.src.models.clean_release
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.audit_service
# @RELATION: [DEPENDS_ON] ->[RepositoryRelations]
# @RELATION: [DEPENDS_ON] ->[ApprovalService]
# @RELATION: [DEPENDS_ON] ->[CleanReleaseModels]
# @RELATION: [DEPENDS_ON] ->[AuditService]
# @INVARIANT: Publication records are append-only snapshots; revoke mutates only publication status for targeted record.
from __future__ import annotations
@@ -27,12 +27,16 @@ from .repository import CleanReleaseRepository
# @PURPOSE: Provide in-memory append-only publication storage.
# @PRE: repository is initialized.
# @POST: Returns publication list attached to repository.
def _get_or_init_publications_store(repository: CleanReleaseRepository) -> List[PublicationRecord]:
def _get_or_init_publications_store(
repository: CleanReleaseRepository,
) -> List[PublicationRecord]:
publications = getattr(repository, "publication_records", None)
if publications is None:
publications = []
setattr(repository, "publication_records", publications)
return publications
# [/DEF:_get_or_init_publications_store:Function]
@@ -44,10 +48,20 @@ def _latest_publication_for_candidate(
repository: CleanReleaseRepository,
candidate_id: str,
) -> PublicationRecord | None:
records = [item for item in _get_or_init_publications_store(repository) if item.candidate_id == candidate_id]
records = [
item
for item in _get_or_init_publications_store(repository)
if item.candidate_id == candidate_id
]
if not records:
return None
return sorted(records, key=lambda item: item.published_at or datetime.min.replace(tzinfo=timezone.utc), reverse=True)[0]
return sorted(
records,
key=lambda item: item.published_at or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)[0]
# [/DEF:_latest_publication_for_candidate:Function]
@@ -55,12 +69,20 @@ def _latest_publication_for_candidate(
# @PURPOSE: Resolve latest approval decision from repository decision store.
# @PRE: candidate_id is non-empty.
# @POST: Returns latest decision object or None.
def _latest_approval_for_candidate(repository: CleanReleaseRepository, candidate_id: str):
def _latest_approval_for_candidate(
repository: CleanReleaseRepository, candidate_id: str
):
decisions = getattr(repository, "approval_decisions", [])
scoped = [item for item in decisions if item.candidate_id == candidate_id]
if not scoped:
return None
return sorted(scoped, key=lambda item: item.decided_at or datetime.min.replace(tzinfo=timezone.utc), reverse=True)[0]
return sorted(
scoped,
key=lambda item: item.decided_at or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)[0]
# [/DEF:_latest_approval_for_candidate:Function]
@@ -78,7 +100,9 @@ def publish_candidate(
publication_ref: str | None = None,
) -> PublicationRecord:
with belief_scope("publication_service.publish_candidate"):
logger.reason(f"[REASON] Evaluating publish gate candidate_id={candidate_id} report_id={report_id}")
logger.reason(
f"[REASON] Evaluating publish gate candidate_id={candidate_id} report_id={report_id}"
)
if not published_by or not published_by.strip():
raise PublicationGateError("published_by must be non-empty")
@@ -96,11 +120,17 @@ def publish_candidate(
raise PublicationGateError("report belongs to another candidate")
latest_approval = _latest_approval_for_candidate(repository, candidate_id)
if latest_approval is None or latest_approval.decision != ApprovalDecisionType.APPROVED.value:
if (
latest_approval is None
or latest_approval.decision != ApprovalDecisionType.APPROVED.value
):
raise PublicationGateError("publish requires APPROVED decision")
latest_publication = _latest_publication_for_candidate(repository, candidate_id)
if latest_publication is not None and latest_publication.status == PublicationStatus.ACTIVE.value:
if (
latest_publication is not None
and latest_publication.status == PublicationStatus.ACTIVE.value
):
raise PublicationGateError("candidate already has active publication")
if candidate.status == CandidateStatus.APPROVED.value:
@@ -108,7 +138,9 @@ def publish_candidate(
candidate.transition_to(CandidateStatus.PUBLISHED)
repository.save_candidate(candidate)
except Exception as exc: # noqa: BLE001
logger.explore(f"[EXPLORE] Candidate transition to PUBLISHED failed candidate_id={candidate_id}: {exc}")
logger.explore(
f"[EXPLORE] Candidate transition to PUBLISHED failed candidate_id={candidate_id}: {exc}"
)
raise PublicationGateError(str(exc)) from exc
record = PublicationRecord(
@@ -122,9 +154,15 @@ def publish_candidate(
status=PublicationStatus.ACTIVE.value,
)
_get_or_init_publications_store(repository).append(record)
audit_preparation(candidate_id, "PUBLISHED", repository=repository, actor=published_by)
logger.reflect(f"[REFLECT] Publication persisted candidate_id={candidate_id} publication_id={record.id}")
audit_preparation(
candidate_id, "PUBLISHED", repository=repository, actor=published_by
)
logger.reflect(
f"[REFLECT] Publication persisted candidate_id={candidate_id} publication_id={record.id}"
)
return record
# [/DEF:publish_candidate:Function]
@@ -140,7 +178,9 @@ def revoke_publication(
comment: str | None = None,
) -> PublicationRecord:
with belief_scope("publication_service.revoke_publication"):
logger.reason(f"[REASON] Evaluating revoke gate publication_id={publication_id}")
logger.reason(
f"[REASON] Evaluating revoke gate publication_id={publication_id}"
)
if not revoked_by or not revoked_by.strip():
raise PublicationGateError("revoked_by must be non-empty")
@@ -168,6 +208,8 @@ def revoke_publication(
)
logger.reflect(f"[REFLECT] Publication revoked publication_id={publication_id}")
return record
# [/DEF:revoke_publication:Function]
# [/DEF:backend.src.services.clean_release.publication_service:Module]
# [/DEF:backend.src.services.clean_release.publication_service:Module]