Files
ss-tools/backend/src/api/routes/clean_release_v2.py
busya 8e8a3c3235 feat: attention-optimized semantic protocol v2.7
Core changes:
- Add @defgroup/@ingroup to 1791 C2+ contracts (555 files) for HCA 128× pre-training DSA grouping
- Add §0.1 Pre-Training Frequency matrix to semantics-core
- Add §VIII Attention Architecture rules (ATTN_1-4) with MLA/CSA/HCA/DSA mechanics
- Add @defgroup/@ingroup to canonical syntax (§II) and all contract examples

Agent prompts (5 files):
- Add ZERO-STATE RATIONALE with MLA/CSA/HCA/DSA compression mechanics
- Add pre-training note: @RATIONALE/@REJECTED are in-context learned tags
- svelte-coder: add missing #region contract, fix Svelte rule violations
- python-coder/fullstack-coder: honor function contracts from speckit plan
- qa-tester: add attention compliance audit (P3 ATTN_1-4 checks)

Skills (6 files):
- Translate all axiom_config descriptions to English
- Fix doc_dirs to index .opencode/ and .specify/
- Deduplicate 5× complexity_rules → single global_tags catalog
- Reduce semantics-svelte 591→485 lines (remove duplicate code blocks)
- Fix semantics-testing: 'Short IDs' → 'Short hierarchical IDs'
- Fix all examples: flat IDs → hierarchical Domain.Name format
- Fix Svelte examples: replace raw Tailwind + <button> with semantic tokens + /ui

Speckit workflow (commands + templates):
- speckit.plan: add Function-Level Contracts for C3+ with @PRE/@POST/@TEST_EDGE
- speckit.plan: add Attention Compliance Gate (ATTN_1-4 before contract generation)
- speckit.tasks: add function contract inlining format (constraints in task description)
- speckit.specify: load semantics-core for spec density rules
- spec-template: add #region contract, @SEMANTICS grouping, hierarchical IDs
- ux-reference-template: add #region wrapper
- plan-template: add attention gate, @defgroup/@ingroup guidance
- tasks-template: add attention audit + rebuild + orphan check tasks
- constitution.md: translate to English, add Principle VIII (attention-optimized contracts)

Reference modules rewritten (hierarchical IDs + full contracts):
- Auth.Jwt: 6 child contracts with @RATIONALE/@REJECTED/@TEST_EDGE
- Api.Auth: 5 endpoints with @TEST_EDGE + molecular CoT markers
- Migration.Model: @defgroup Migration with 18 @ACTION + 6 @INVARIANT

Scripts:
- add_defgroup_ingroup.py: zero-risk additive @ingroup migration (1791 insertions)
- migrate_hierarchical.py: flat→hierarchical ID dry-run analysis (792 contracts)
- merge_prompts.py: merge all prompts/skills/commands into one review file

Config:
- axiom_config.yaml: 749→395 lines (-47%), English, doc_dirs include prompts
- Fix test_datasets.py import collision (rename → test_datasets_routes.py)
- Fix test_preview.py: SupersetClient→get_superset_client, AsyncMock, logger f-string
2026-06-08 16:30:59 +03:00

329 lines
10 KiB
Python

# #region CleanReleaseV2Api [C:4] [TYPE Module] [SEMANTICS fastapi, clean-release, candidate, lifecycle, api]
# @defgroup Api Module group.
# @BRIEF Redesigned clean release API for headless candidate lifecycle.
# @LAYER API
# @RELATION DEPENDS_ON -> [CleanReleaseRepository]
# @RELATION CALLS -> [approve_candidate]
# @RELATION CALLS -> [publish_candidate]
# @PRE Clean release repository dependency is available for candidate lifecycle endpoints.
# @POST Candidate registration, approval, publication, and revocation routes are registered without behavior changes.
# @SIDE_EFFECT Persists candidate lifecycle state through clean release services and repository adapters.
from datetime import UTC, datetime
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, status
from ...dependencies import get_clean_release_repository
from ...models.clean_release import (
CandidateArtifact,
DistributionManifest,
ReleaseCandidate,
)
from ...services.clean_release.approval_service import (
approve_candidate,
reject_candidate,
)
from ...services.clean_release.dto import CandidateDTO, ManifestDTO
from ...services.clean_release.enums import CandidateStatus
from ...services.clean_release.publication_service import (
publish_candidate,
revoke_publication,
)
from ...services.clean_release.repository import CleanReleaseRepository
router = APIRouter(prefix="/api/v2/clean-release", tags=["Clean Release V2"])
# #region ApprovalRequest [C:1] [TYPE Class]
# @BRIEF Schema for approval request payload.
class ApprovalRequest(dict):
pass
# #endregion ApprovalRequest
# #region PublishRequest [C:1] [TYPE Class]
# @BRIEF Schema for publication request payload.
class PublishRequest(dict):
pass
# #endregion PublishRequest
# #region RevokeRequest [C:1] [TYPE Class]
# @BRIEF Schema for revocation request payload.
class RevokeRequest(dict):
pass
# #endregion RevokeRequest
# #region register_candidate [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Register a new release candidate.
# @PRE Payload contains required fields (id, version, source_snapshot_ref, created_by).
# @POST Candidate is saved in repository.
# @RELATION DEPENDS_ON -> [CleanReleaseRepository]
# @RELATION DEPENDS_ON -> [clean_release_dto]
@router.post(
"/candidates", response_model=CandidateDTO, status_code=status.HTTP_201_CREATED
)
async def register_candidate(
payload: dict[str, Any],
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
candidate = ReleaseCandidate(
id=payload["id"],
version=payload["version"],
source_snapshot_ref=payload["source_snapshot_ref"],
created_by=payload["created_by"],
created_at=datetime.now(UTC),
status=CandidateStatus.DRAFT.value,
)
repository.save_candidate(candidate)
return CandidateDTO(
id=candidate.id,
version=candidate.version,
source_snapshot_ref=candidate.source_snapshot_ref,
created_at=candidate.created_at,
created_by=candidate.created_by,
status=CandidateStatus(candidate.status),
)
# #endregion register_candidate
# #region import_artifacts [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Associate artifacts with a release candidate.
# @PRE Candidate exists.
# @POST Artifacts are processed (placeholder).
# @RELATION DEPENDS_ON -> [CleanReleaseRepository]
@router.post("/candidates/{candidate_id}/artifacts")
async def import_artifacts(
candidate_id: str,
payload: dict[str, Any],
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
candidate = repository.get_candidate(candidate_id)
if not candidate:
raise HTTPException(status_code=404, detail="Candidate not found")
for art_data in payload.get("artifacts", []):
artifact = CandidateArtifact(
id=art_data["id"],
candidate_id=candidate_id,
path=art_data["path"],
sha256=art_data["sha256"],
size=art_data["size"],
)
# In a real repo we'd have save_artifact
# repository.save_artifact(artifact)
pass
return {"status": "success"}
# #endregion import_artifacts
# #region build_manifest [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Generate distribution manifest for a candidate.
# @PRE Candidate exists.
# @POST Manifest is created and saved.
# @RELATION DEPENDS_ON -> [CleanReleaseRepository]
@router.post(
"/candidates/{candidate_id}/manifests",
response_model=ManifestDTO,
status_code=status.HTTP_201_CREATED,
)
async def build_manifest(
candidate_id: str,
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
candidate = repository.get_candidate(candidate_id)
if not candidate:
raise HTTPException(status_code=404, detail="Candidate not found")
manifest = DistributionManifest(
id=f"manifest-{candidate_id}",
candidate_id=candidate_id,
manifest_version=1,
manifest_digest="hash-123",
artifacts_digest="art-hash-123",
created_by="system",
created_at=datetime.now(UTC),
source_snapshot_ref=candidate.source_snapshot_ref,
content_json={"items": [], "summary": {}},
)
repository.save_manifest(manifest)
return ManifestDTO(
id=manifest.id,
candidate_id=manifest.candidate_id,
manifest_version=manifest.manifest_version,
manifest_digest=manifest.manifest_digest,
artifacts_digest=manifest.artifacts_digest,
created_at=manifest.created_at,
created_by=manifest.created_by,
source_snapshot_ref=manifest.source_snapshot_ref,
content_json=manifest.content_json,
)
# #endregion build_manifest
# #region approve_candidate_endpoint [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Endpoint to record candidate approval.
# @RELATION CALLS -> [approve_candidate]
@router.post("/candidates/{candidate_id}/approve")
async def approve_candidate_endpoint(
candidate_id: str,
payload: dict[str, Any],
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
try:
decision = approve_candidate(
repository=repository,
candidate_id=candidate_id,
report_id=str(payload["report_id"]),
decided_by=str(payload["decided_by"]),
comment=payload.get("comment"),
)
except Exception as exc:
raise HTTPException(
status_code=409, detail={"message": str(exc), "code": "APPROVAL_GATE_ERROR"}
)
return {"status": "ok", "decision": decision.decision, "decision_id": decision.id}
# #endregion approve_candidate_endpoint
# #region reject_candidate_endpoint [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Endpoint to record candidate rejection.
# @RELATION CALLS -> [reject_candidate]
@router.post("/candidates/{candidate_id}/reject")
async def reject_candidate_endpoint(
candidate_id: str,
payload: dict[str, Any],
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
try:
decision = reject_candidate(
repository=repository,
candidate_id=candidate_id,
report_id=str(payload["report_id"]),
decided_by=str(payload["decided_by"]),
comment=payload.get("comment"),
)
except Exception as exc:
raise HTTPException(
status_code=409, detail={"message": str(exc), "code": "APPROVAL_GATE_ERROR"}
)
return {"status": "ok", "decision": decision.decision, "decision_id": decision.id}
# #endregion reject_candidate_endpoint
# #region publish_candidate_endpoint [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Endpoint to publish an approved candidate.
# @RELATION CALLS -> [publish_candidate]
@router.post("/candidates/{candidate_id}/publish")
async def publish_candidate_endpoint(
candidate_id: str,
payload: dict[str, Any],
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
try:
publication = publish_candidate(
repository=repository,
candidate_id=candidate_id,
report_id=str(payload["report_id"]),
published_by=str(payload["published_by"]),
target_channel=str(payload["target_channel"]),
publication_ref=payload.get("publication_ref"),
)
except Exception as exc:
raise HTTPException(
status_code=409,
detail={"message": str(exc), "code": "PUBLICATION_GATE_ERROR"},
)
return {
"status": "ok",
"publication": {
"id": publication.id,
"candidate_id": publication.candidate_id,
"report_id": publication.report_id,
"published_by": publication.published_by,
"published_at": publication.published_at.isoformat()
if publication.published_at
else None,
"target_channel": publication.target_channel,
"publication_ref": publication.publication_ref,
"status": publication.status,
},
}
# #endregion publish_candidate_endpoint
# #region revoke_publication_endpoint [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Endpoint to revoke a previous publication.
# @RELATION CALLS -> [revoke_publication]
@router.post("/publications/{publication_id}/revoke")
async def revoke_publication_endpoint(
publication_id: str,
payload: dict[str, Any],
repository: CleanReleaseRepository = Depends(get_clean_release_repository),
):
try:
publication = revoke_publication(
repository=repository,
publication_id=publication_id,
revoked_by=str(payload["revoked_by"]),
comment=payload.get("comment"),
)
except Exception as exc:
raise HTTPException(
status_code=409,
detail={"message": str(exc), "code": "PUBLICATION_GATE_ERROR"},
)
return {
"status": "ok",
"publication": {
"id": publication.id,
"candidate_id": publication.candidate_id,
"report_id": publication.report_id,
"published_by": publication.published_by,
"published_at": publication.published_at.isoformat()
if publication.published_at
else None,
"target_channel": publication.target_channel,
"publication_ref": publication.publication_ref,
"status": publication.status,
},
}
# #endregion revoke_publication_endpoint
# #endregion CleanReleaseV2Api