diff --git a/specs/025-clean-release-compliance/checklists/requirements.md b/specs/025-clean-release-compliance/checklists/requirements.md new file mode 100644 index 00000000..0899696e --- /dev/null +++ b/specs/025-clean-release-compliance/checklists/requirements.md @@ -0,0 +1,36 @@ +# Specification Quality Checklist: Clean Release Compliance Subsystem Redesign + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-03-09 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, file structure) drive the requirements +- [x] Focused on operator value, governance, auditability, and release workflow outcomes +- [x] Written for product/release stakeholders, not only for implementers +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance intent +- [x] User scenarios cover primary lifecycle flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No blocking ambiguity remains for `/speckit.plan` +- [x] Specification is ready for `/speckit.plan` and `/speckit.tasks` + +## Notes + +- Architectural direction is intentional because the feature itself is a subsystem redesign rather than a small end-user capability. +- Trust model, lifecycle invariants, and immutable evidence were kept at the requirement level because they are the product value of this redesign. diff --git a/specs/025-clean-release-compliance/contracts/clean-release-api.openapi.yaml b/specs/025-clean-release-compliance/contracts/clean-release-api.openapi.yaml new file mode 100644 index 00000000..98f5123c --- /dev/null +++ b/specs/025-clean-release-compliance/contracts/clean-release-api.openapi.yaml @@ -0,0 +1,714 @@ +openapi: 3.1.0 +info: + title: Clean Release API + version: 0.1.0 + description: API-first contract for clean release candidate lifecycle, compliance runs, approvals and publications. +servers: + - url: /api +x-interface-actor-mapping: + description: External API request payloads use domain-specific actor fields; interface adapters may accept a unified actor context internally but must persist canonical *_by fields. + mappings: + candidate_register: created_by + manifest_build: created_by + compliance_run: requested_by + approval_or_reject: decided_by + publish: published_by + revoke: actor +paths: + /clean-release/candidates: + post: + summary: Register a release candidate + operationId: registerCleanReleaseCandidate + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterCandidateRequest' + responses: + '201': + description: Candidate created + content: + application/json: + schema: + $ref: '#/components/schemas/CandidateOverview' + get: + summary: List release candidates + operationId: listCleanReleaseCandidates + responses: + '200': + description: Candidate list + content: + application/json: + schema: + type: object + required: [items] + properties: + items: + type: array + items: + $ref: '#/components/schemas/CandidateOverview' + /clean-release/candidates/{candidate_id}: + get: + summary: Get candidate overview + operationId: getCleanReleaseCandidate + parameters: + - $ref: '#/components/parameters/CandidateId' + responses: + '200': + description: Candidate overview + content: + application/json: + schema: + $ref: '#/components/schemas/CandidateOverview' + '404': + $ref: '#/components/responses/NotFound' + /clean-release/candidates/{candidate_id}/artifacts/import: + post: + summary: Import candidate artifacts + operationId: importCleanReleaseArtifacts + parameters: + - $ref: '#/components/parameters/CandidateId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ImportArtifactsRequest' + responses: + '200': + description: Artifacts imported + content: + application/json: + schema: + type: object + required: [candidate_id, imported_count, status] + properties: + candidate_id: + type: string + imported_count: + type: integer + status: + type: string + /clean-release/candidates/{candidate_id}/artifacts: + get: + summary: List candidate artifacts + operationId: listCleanReleaseArtifacts + parameters: + - $ref: '#/components/parameters/CandidateId' + responses: + '200': + description: Candidate artifacts + content: + application/json: + schema: + type: object + required: [items] + properties: + items: + type: array + items: + $ref: '#/components/schemas/CandidateArtifact' + /clean-release/candidates/{candidate_id}/manifest: + post: + summary: Build a new manifest snapshot + operationId: buildCleanReleaseManifest + parameters: + - $ref: '#/components/parameters/CandidateId' + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + requested_by: + type: string + responses: + '201': + description: Manifest created + content: + application/json: + schema: + $ref: '#/components/schemas/DistributionManifest' + /clean-release/candidates/{candidate_id}/manifests: + get: + summary: List manifests for candidate + operationId: listCleanReleaseManifests + parameters: + - $ref: '#/components/parameters/CandidateId' + responses: + '200': + description: Candidate manifests + content: + application/json: + schema: + type: object + required: [items] + properties: + items: + type: array + items: + $ref: '#/components/schemas/DistributionManifest' + /clean-release/manifests/{manifest_id}: + get: + summary: Get manifest snapshot + operationId: getCleanReleaseManifest + parameters: + - name: manifest_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Manifest snapshot + content: + application/json: + schema: + $ref: '#/components/schemas/DistributionManifest' + '404': + $ref: '#/components/responses/NotFound' + /clean-release/candidates/{candidate_id}/compliance-runs: + post: + summary: Request a compliance run + operationId: createCleanReleaseComplianceRun + parameters: + - $ref: '#/components/parameters/CandidateId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComplianceRunRequest' + responses: + '202': + description: Compliance run accepted + content: + application/json: + schema: + $ref: '#/components/schemas/ComplianceRun' + '404': + $ref: '#/components/responses/NotFound' + '409': + $ref: '#/components/responses/Conflict' + /clean-release/compliance-runs/{run_id}: + get: + summary: Get compliance run status + operationId: getCleanReleaseComplianceRun + parameters: + - name: run_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Compliance run + content: + application/json: + schema: + $ref: '#/components/schemas/ComplianceRun' + '404': + $ref: '#/components/responses/NotFound' + /clean-release/compliance-runs/{run_id}/stages: + get: + summary: List stage results for a run + operationId: listCleanReleaseComplianceStages + parameters: + - name: run_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Stage list + content: + application/json: + schema: + type: object + required: [items] + properties: + items: + type: array + items: + $ref: '#/components/schemas/ComplianceStageRun' + /clean-release/compliance-runs/{run_id}/violations: + get: + summary: List violations for a run + operationId: listCleanReleaseComplianceViolations + parameters: + - name: run_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Violation list + content: + application/json: + schema: + type: object + required: [items] + properties: + items: + type: array + items: + $ref: '#/components/schemas/ComplianceViolation' + /clean-release/compliance-runs/{run_id}/report: + get: + summary: Get final report for a run + operationId: getCleanReleaseComplianceReport + parameters: + - name: run_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Compliance report + content: + application/json: + schema: + $ref: '#/components/schemas/ComplianceReport' + '404': + $ref: '#/components/responses/NotFound' + /clean-release/candidates/{candidate_id}/approve: + post: + summary: Approve a candidate using a report + operationId: approveCleanReleaseCandidate + parameters: + - $ref: '#/components/parameters/CandidateId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovalRequest' + responses: + '200': + description: Approval created + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovalDecision' + '409': + $ref: '#/components/responses/Conflict' + /clean-release/candidates/{candidate_id}/reject: + post: + summary: Reject a candidate using a report + operationId: rejectCleanReleaseCandidate + parameters: + - $ref: '#/components/parameters/CandidateId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovalRequest' + responses: + '200': + description: Rejection created + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovalDecision' + '409': + $ref: '#/components/responses/Conflict' + /clean-release/candidates/{candidate_id}/publish: + post: + summary: Publish an approved candidate + operationId: publishCleanReleaseCandidate + parameters: + - $ref: '#/components/parameters/CandidateId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + responses: + '200': + description: Publication created + content: + application/json: + schema: + $ref: '#/components/schemas/PublicationRecord' + '409': + $ref: '#/components/responses/Conflict' + /clean-release/publications/{publication_id}/revoke: + post: + summary: Revoke a publication + operationId: revokeCleanReleasePublication + parameters: + - name: publication_id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RevokePublicationRequest' + responses: + '200': + description: Publication revoked + content: + application/json: + schema: + $ref: '#/components/schemas/PublicationRecord' + '404': + $ref: '#/components/responses/NotFound' + '409': + $ref: '#/components/responses/Conflict' +components: + parameters: + CandidateId: + name: candidate_id + in: path + required: true + schema: + type: string + responses: + NotFound: + description: Entity not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Conflict: + description: Illegal lifecycle transition or conflicting state + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + schemas: + RegisterCandidateRequest: + type: object + required: [candidate_id, version, source_snapshot_ref, created_by] + properties: + candidate_id: + type: string + version: + type: string + source_snapshot_ref: + type: string + build_id: + type: string + created_by: + type: string + provenance_ref: + type: string + ImportArtifactsRequest: + type: object + required: [artifacts] + properties: + artifacts: + type: array + items: + $ref: '#/components/schemas/CandidateArtifactInput' + CandidateArtifactInput: + type: object + required: [path, sha256, size] + properties: + path: + type: string + sha256: + type: string + size: + type: integer + declared_category: + type: string + detected_category: + type: string + source_uri: + type: string + source_host: + type: string + metadata: + type: object + additionalProperties: true + CreateComplianceRunRequest: + type: object + required: [requested_by] + properties: + manifest_id: + type: string + requested_by: + type: string + ApprovalRequest: + type: object + required: [report_id, decided_by] + properties: + report_id: + type: string + decided_by: + type: string + comment: + type: string + PublishRequest: + type: object + required: [report_id, target_channel, actor] + properties: + report_id: + type: string + target_channel: + type: string + actor: + type: string + RevokePublicationRequest: + type: object + required: [actor, reason] + properties: + actor: + type: string + reason: + type: string + CandidateOverview: + type: object + required: [candidate_id, version, source_snapshot_ref, status] + properties: + candidate_id: + type: string + version: + type: string + source_snapshot_ref: + type: string + status: + type: string + latest_manifest_id: + type: string + latest_manifest_digest: + type: string + latest_run_id: + type: string + latest_run_status: + type: string + latest_report_id: + type: string + latest_report_final_status: + type: string + latest_policy_snapshot_id: + type: string + latest_policy_version: + type: string + latest_registry_snapshot_id: + type: string + latest_registry_version: + type: string + latest_approval_decision: + type: string + latest_publication_id: + type: string + latest_publication_status: + type: string + CandidateArtifact: + type: object + required: [id, candidate_id, path, sha256, size, metadata] + properties: + id: + type: string + candidate_id: + type: string + path: + type: string + sha256: + type: string + size: + type: integer + detected_category: + type: string + declared_category: + type: string + source_uri: + type: string + source_host: + type: string + metadata: + type: object + additionalProperties: true + DistributionManifest: + type: object + required: [id, candidate_id, manifest_version, manifest_digest, artifacts_digest, created_at, created_by, source_snapshot_ref, content_json, immutable] + properties: + id: + type: string + candidate_id: + type: string + manifest_version: + type: integer + manifest_digest: + type: string + artifacts_digest: + type: string + created_at: + type: string + format: date-time + created_by: + type: string + source_snapshot_ref: + type: string + content_json: + type: object + additionalProperties: true + immutable: + type: boolean + ComplianceRun: + type: object + required: [run_id, candidate_id, status, task_id] + properties: + run_id: + type: string + candidate_id: + type: string + manifest_id: + type: string + manifest_digest: + type: string + policy_snapshot_id: + type: string + registry_snapshot_id: + type: string + requested_by: + type: string + requested_at: + type: string + format: date-time + started_at: + type: string + format: date-time + finished_at: + type: string + format: date-time + status: + type: string + final_status: + type: string + failure_reason: + type: string + report_id: + type: string + task_id: + type: string + ComplianceStageRun: + type: object + required: [id, run_id, stage_name, status, details_json] + properties: + id: + type: string + run_id: + type: string + stage_name: + type: string + status: + type: string + started_at: + type: string + format: date-time + finished_at: + type: string + format: date-time + decision: + type: string + details_json: + type: object + additionalProperties: true + ComplianceViolation: + type: object + required: [id, run_id, stage_name, code, severity, message, evidence_json] + properties: + id: + type: string + run_id: + type: string + stage_name: + type: string + code: + type: string + severity: + type: string + artifact_path: + type: string + artifact_sha256: + type: string + message: + type: string + evidence_json: + type: object + additionalProperties: true + ComplianceReport: + type: object + required: [report_id, run_id, candidate_id, final_status, summary_json, generated_at, immutable] + properties: + report_id: + type: string + run_id: + type: string + candidate_id: + type: string + final_status: + type: string + summary_json: + type: object + additionalProperties: true + generated_at: + type: string + format: date-time + immutable: + type: boolean + ApprovalDecision: + type: object + required: [id, candidate_id, report_id, decision, decided_by, decided_at] + properties: + id: + type: string + candidate_id: + type: string + report_id: + type: string + decision: + type: string + decided_by: + type: string + decided_at: + type: string + format: date-time + comment: + type: string + PublicationRecord: + type: object + required: [id, candidate_id, report_id, published_by, published_at, target_channel, status] + properties: + id: + type: string + candidate_id: + type: string + report_id: + type: string + published_by: + type: string + published_at: + type: string + format: date-time + target_channel: + type: string + publication_ref: + type: string + status: + type: string + ErrorResponse: + type: object + required: [code, message] + properties: + code: + type: string + message: + type: string + details: + type: object + additionalProperties: true diff --git a/specs/025-clean-release-compliance/contracts/cli.md b/specs/025-clean-release-compliance/contracts/cli.md new file mode 100644 index 00000000..be69463a --- /dev/null +++ b/specs/025-clean-release-compliance/contracts/cli.md @@ -0,0 +1,78 @@ +# CLI Contract: Clean Release Compliance Subsystem Redesign + +## Command Groups + +### Candidate + +```bash +clean-release candidate register --candidate-id --version --source-snapshot [--build-id ] [--provenance-ref ] [--actor ] +clean-release candidate import-artifacts --candidate-id --input [--actor ] +clean-release candidate show --candidate-id [--json] +clean-release candidate list [--json] +``` + +### Manifest + +```bash +clean-release manifest build --candidate-id [--actor ] [--json] +clean-release manifest show --manifest-id [--json] +clean-release manifest list --candidate-id [--json] +``` + +### Compliance + +```bash +clean-release compliance run --candidate-id [--manifest-id ] [--actor ] [--json] +clean-release compliance status --run-id [--json] +clean-release compliance report --run-id [--json] +clean-release compliance violations --run-id [--json] +``` + +### Release + +```bash +clean-release release approve --candidate-id --report-id --actor [--comment ] [--json] +clean-release release reject --candidate-id --report-id --actor [--comment ] [--json] +clean-release release publish --candidate-id --report-id --channel --actor [--json] +clean-release release revoke --publication-id --actor --reason [--json] +``` + +### Demo + +```bash +clean-release demo seed [--profile ] [--json] +clean-release demo reset [--json] +``` + +## Output Rules + +- Default mode prints concise operator-friendly summaries. +- `--json` prints machine-readable DTO payloads. +- Errors print machine-readable codes and short text to stderr. +- Compliance run creation returns `run_id` and `task_id` immediately. +- If `--manifest-id` is omitted, CLI uses the latest manifest for the candidate or returns invalid input when no manifest exists. + +## Actor Mapping Rule + +- CLI always accepts external actor context as `--actor`. +- Interface adapters map `--actor` to internal domain fields by action type: + - candidate register -> `created_by` + - manifest build -> `created_by` + - compliance run -> `requested_by` + - release approve/reject -> `decided_by` + - release publish -> `published_by` + - release revoke -> revocation actor field in command payload or audit event +- This mapping is deterministic and hidden from operators; CLI does not expose multiple actor flag names for different commands. + +## Exit Codes + +- `0`: Passed / successful mutation / successful read. +- `1`: Business blocked (`BLOCKED`, forbidden publish/approve because of valid business rule). +- `2`: Invalid input (`candidate not found`, `manifest missing`, malformed request). +- `3`: System error (`policy store unavailable`, persistence failure, unexpected exception). + +## CLI Behavior Constraints + +- Business actions are explicit CLI arguments, not env-driven side effects. +- CLI supports headless operation and never requires curses/TTY. +- CLI does not synthesize policy or registry values locally. diff --git a/specs/025-clean-release-compliance/contracts/modules.md b/specs/025-clean-release-compliance/contracts/modules.md new file mode 100644 index 00000000..6c6810de --- /dev/null +++ b/specs/025-clean-release-compliance/contracts/modules.md @@ -0,0 +1,391 @@ +# Module Contracts: Clean Release Compliance Subsystem Redesign + +## Backend Domain Models Contract + +# [DEF:CleanReleaseDomainModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, domain, lifecycle, immutability, evidence] +# @PURPOSE: Define canonical clean release entities, lifecycle states and evidence invariants for candidate, manifest, run, report, approval and publication records. +# @LAYER: Domain +# @RELATION: DEPENDS_ON -> [DEF:TaskManagerModule] +# @INVARIANT: Immutable snapshots are never mutated after creation; forbidden lifecycle transitions are rejected. +# @PRE: Lifecycle commands reference existing candidate/report/publication identifiers when required. +# @POST: State transitions are valid and terminal evidence remains readable. +# @TEST_CONTRACT: CleanReleaseDomainModule -> {Input: lifecycle command + current aggregate, Output: updated aggregate or domain error} +# @TEST_FIXTURE: candidate_state_machine -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","status":"CHECK_PASSED"} +# @TEST_EDGE: approve_without_passed_report -> reject transition +# @TEST_EDGE: publish_without_approval -> reject transition +# @TEST_EDGE: mutate_existing_manifest -> reject update +# @TEST_INVARIANT: lifecycle_integrity -> VERIFIED_BY: [approve_without_passed_report, publish_without_approval, mutate_existing_manifest] +# [/DEF:CleanReleaseDomainModule] + +--- + +## Backend Clean Release Facade Contract + +# [DEF:CleanReleaseFacadeModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, facade, orchestration, dto] +# @PURPOSE: Expose one stable application facade for CLI, TUI, REST API and future Web UI over the clean release lifecycle. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:CandidatePreparationServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:ManifestServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:PolicyResolutionServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:ComplianceExecutionServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:ApprovalServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:PublicationServiceModule] +# @INVARIANT: Interface adapters never bypass facade for business mutations. +# @PRE: Incoming commands are validated and include actor context. +# @POST: Returns DTOs with enough data for thin clients to render without local business recomputation. +# @TEST_CONTRACT: CleanReleaseFacadeModule -> {Input: validated command, Output: CandidateOverviewDTO|ManifestDTO|ComplianceRunDTO|ReportDTO|ApprovalDTO|PublicationDTO} +# @TEST_FIXTURE: facade_happy_path -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","actor":"release-bot"} +# @TEST_EDGE: missing_candidate -> returns explicit not-found error +# @TEST_EDGE: illegal_transition -> returns transition error +# @TEST_EDGE: missing_policy_snapshot -> returns trusted-source error +# @TEST_INVARIANT: thin_client_boundary -> VERIFIED_BY: [missing_candidate, illegal_transition, missing_policy_snapshot] +# [/DEF:CleanReleaseFacadeModule] + +--- + +## Backend Candidate Preparation Service Contract + +# [DEF:CandidatePreparationServiceModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, candidate, artifacts, preparation] +# @PURPOSE: Register release candidates, import artifacts, validate artifact sets and progress candidates to PREPARED. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:CleanReleaseDomainModule] +# @RELATION: DEPENDS_ON -> [DEF:CandidateRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:ArtifactRepositoryModule] +# @INVARIANT: Candidate preparation does not run compliance checks or synthesize manifests implicitly. +# @PRE: Candidate identifiers are unique and artifact payloads are structurally valid. +# @POST: Candidate and artifact records are persisted and candidate status advances only through allowed states. +# @TEST_CONTRACT: CandidatePreparationServiceModule -> {Input: candidate payload + artifacts, Output: candidate aggregate} +# @TEST_FIXTURE: prepared_candidate -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","artifacts":[{"path":"dist/app.whl","sha256":"abc"}]} +# @TEST_EDGE: duplicate_candidate_id -> reject create +# @TEST_EDGE: malformed_artifact_payload -> reject import +# @TEST_EDGE: empty_artifact_set -> reject mark_prepared +# @TEST_INVARIANT: candidate_input_integrity -> VERIFIED_BY: [duplicate_candidate_id, malformed_artifact_payload, empty_artifact_set] +# [/DEF:CandidatePreparationServiceModule] + +--- + +## Backend Manifest Service Contract + +# [DEF:ManifestServiceModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, manifest, digest, immutability] +# @PURPOSE: Build immutable manifest snapshots and return latest manifest views for candidates. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:CandidateRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:ArtifactRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:ManifestRepositoryModule] +# @INVARIANT: Existing manifest versions remain immutable; rebuild creates a new version. +# @PRE: Candidate exists and artifact set is valid for manifest generation. +# @POST: New manifest digest is deterministic for the selected artifact set. +# @TEST_CONTRACT: ManifestServiceModule -> {Input: candidate_id, Output: DistributionManifest} +# @TEST_FIXTURE: manifest_rebuild -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","manifest_version":1} +# @TEST_EDGE: build_without_candidate -> reject request +# @TEST_EDGE: build_with_changed_artifacts -> create new version +# @TEST_EDGE: overwrite_existing_manifest -> reject mutation +# @TEST_INVARIANT: manifest_snapshot_integrity -> VERIFIED_BY: [build_without_candidate, build_with_changed_artifacts, overwrite_existing_manifest] +# [/DEF:ManifestServiceModule] + +--- + +## Backend Policy Resolution Service Contract + +# [DEF:PolicyResolutionServiceModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, policy, registry, trust_model] +# @PURPOSE: Resolve active policy and source registry from trusted read-only stores into immutable snapshots. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:PolicySnapshotRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:TrustedPolicyStoreModule] +# @INVARIANT: No interface-provided payload may alter resolved policy or registry contents. +# @PRE: Requested profile exists in trusted store. +# @POST: Returns immutable policy snapshot and linked registry snapshot. +# @TEST_CONTRACT: PolicyResolutionServiceModule -> {Input: profile, Output: CleanPolicySnapshot + SourceRegistrySnapshot} +# @TEST_FIXTURE: trusted_default_profile -> INLINE_JSON {"profile":"default"} +# @TEST_EDGE: missing_profile -> reject request +# @TEST_EDGE: registry_missing -> reject request +# @TEST_EDGE: ui_override_attempt -> ignore override and fail validation +# @TEST_INVARIANT: trusted_input_boundary -> VERIFIED_BY: [missing_profile, registry_missing, ui_override_attempt] +# [/DEF:PolicyResolutionServiceModule] + +--- + +## Backend Compliance Execution Service Contract + +# [DEF:ComplianceExecutionServiceModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, compliance, task_manager, stages, report] +# @PURPOSE: Create compliance runs, bind them to TaskManager, execute stage pipeline, persist stage results, violations and final reports. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:PolicyResolutionServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:StagePipelineModule] +# @RELATION: DEPENDS_ON -> [DEF:ComplianceRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:ReportRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:AuditServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:TaskManagerModule] +# @INVARIANT: A compliance request becomes exactly one run and at most one final immutable report. +# @PRE: Candidate and manifest exist; trusted snapshots are resolvable. +# @POST: Run state, stage records, violations and report are mutually consistent. +# @POST: API-facing request method is non-blocking and returns run/task identifiers before stage completion. +# @TEST_CONTRACT: ComplianceExecutionServiceModule -> {Input: candidate_id + manifest_id + actor, Output: ComplianceRunDTO or ComplianceReport} +# @TEST_FIXTURE: blocking_run -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","manifest_id":"man-001"} +# @TEST_EDGE: run_without_manifest -> reject request +# @TEST_EDGE: task_crash_mid_run -> final_status ERROR with preserved partial evidence +# @TEST_EDGE: blocked_violation_without_report -> reject finalization +# @TEST_INVARIANT: run_report_consistency -> VERIFIED_BY: [run_without_manifest, task_crash_mid_run, blocked_violation_without_report] +# [/DEF:ComplianceExecutionServiceModule] + +--- + +## Backend Stage Pipeline Contract + +# [DEF:StagePipelineModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, stages, pluggable_pipeline] +# @PURPOSE: Provide pluggable compliance stages with deterministic ordering and structured results. +# @LAYER: Domain +# @RELATION: CALLED_BY -> [DEF:ComplianceExecutionServiceModule] +# @INVARIANT: Mandatory stages execute in stable order unless run stops on terminal error policy. +# @PRE: Compliance context contains candidate, manifest, policy snapshot and registry snapshot. +# @POST: Each stage returns decision, violations and details without mutating trusted snapshots. +# [/DEF:StagePipelineModule] + +--- + +## Backend Approval Service Contract + +# [DEF:ApprovalServiceModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, approval, gate] +# @PURPOSE: Approve or reject candidates based on completed compliance reports. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:ReportRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:ApprovalRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:AuditServiceModule] +# @INVARIANT: Approval is impossible without a valid PASSED report; latest approval decision governs publication gate. +# @PRE: Candidate exists and referenced report belongs to the candidate. +# @POST: Approval or rejection decision is persisted immutably; rejection blocks publication gate without mutating compliance evidence. +# @TEST_CONTRACT: ApprovalServiceModule -> {Input: candidate_id + report_id + actor, Output: ApprovalDecision} +# @TEST_FIXTURE: passed_report -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","report_id":"rpt-001","final_status":"PASSED"} +# @TEST_EDGE: approve_blocked_report -> reject request +# @TEST_EDGE: approve_foreign_report -> reject request +# @TEST_EDGE: duplicate_approve_terminal_state -> reject or preserve existing state deterministically +# @TEST_EDGE: reject_then_publish -> publish remains blocked until a later valid approve +# @TEST_INVARIANT: approval_gate_integrity -> VERIFIED_BY: [approve_blocked_report, approve_foreign_report, duplicate_approve_terminal_state, reject_then_publish] +# [/DEF:ApprovalServiceModule] + +--- + +## Backend Publication Service Contract + +# [DEF:PublicationServiceModule:Module] +# @TIER: CRITICAL +# @SEMANTICS: [clean_release, publication, revoke] +# @PURPOSE: Publish approved candidates to target channels and revoke publications without deleting historical evidence. +# @LAYER: Application +# @RELATION: DEPENDS_ON -> [DEF:ApprovalServiceModule] +# @RELATION: DEPENDS_ON -> [DEF:PublicationRepositoryModule] +# @RELATION: DEPENDS_ON -> [DEF:AuditServiceModule] +# @INVARIANT: Publication is impossible without candidate approval; revoke does not erase original publication record. +# @PRE: Candidate is approved and report is the approved basis for publication. +# @POST: Publication or revocation record is persisted immutably with channel and actor context. +# @TEST_CONTRACT: PublicationServiceModule -> {Input: candidate_id + report_id + channel + actor, Output: PublicationRecord} +# @TEST_FIXTURE: approved_candidate -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","status":"APPROVED"} +# @TEST_EDGE: publish_without_approval -> reject request +# @TEST_EDGE: revoke_unknown_publication -> reject request +# @TEST_EDGE: republish_after_revoke -> deterministic policy required +# @TEST_INVARIANT: publication_gate_integrity -> VERIFIED_BY: [publish_without_approval, revoke_unknown_publication, republish_after_revoke] +# [/DEF:PublicationServiceModule] + +--- + +## Backend Audit Service Contract + +# [DEF:AuditServiceModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, audit, append_only] +# @PURPOSE: Persist append-only audit events for release lifecycle and compliance execution. +# @LAYER: Infra +# @RELATION: CALLED_BY -> [DEF:CandidatePreparationServiceModule] +# @RELATION: CALLED_BY -> [DEF:ManifestServiceModule] +# @RELATION: CALLED_BY -> [DEF:ComplianceExecutionServiceModule] +# @RELATION: CALLED_BY -> [DEF:ApprovalServiceModule] +# @RELATION: CALLED_BY -> [DEF:PublicationServiceModule] +# @INVARIANT: Audit records are append-only in real mode. +# @PRE: Event context contains actor and operation identifiers. +# @POST: One structured audit event is persisted per critical lifecycle mutation. +# [/DEF:AuditServiceModule] + +--- + +## Backend Repository Contracts + +# [DEF:CandidateRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, candidate] +# @PURPOSE: Persist and query release candidates and candidate overview projections. +# @LAYER: Infra +# @INVARIANT: Candidate writes honor lifecycle guards defined in the domain module. +# [/DEF:CandidateRepositoryModule] + +# [DEF:ArtifactRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, artifacts] +# @PURPOSE: Persist and query candidate artifacts with checksum metadata. +# @LAYER: Infra +# @INVARIANT: Artifact checksum/path records remain stable after import. +# [/DEF:ArtifactRepositoryModule] + +# [DEF:ManifestRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, manifest] +# @PURPOSE: Persist immutable manifests and provide latest-version lookup. +# @LAYER: Infra +# @INVARIANT: Existing manifest versions are read-only. +# [/DEF:ManifestRepositoryModule] + +# [DEF:PolicySnapshotRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, policy_snapshot] +# @PURPOSE: Persist immutable policy and registry snapshots used by runs. +# @LAYER: Infra +# @INVARIANT: Snapshot content cannot be mutated after persistence. +# [/DEF:PolicySnapshotRepositoryModule] + +# [DEF:ComplianceRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, run, stage, violation] +# @PURPOSE: Persist compliance runs, stage records and violations. +# @LAYER: Infra +# @INVARIANT: Historical run evidence is append-only in real mode. +# [/DEF:ComplianceRepositoryModule] + +# [DEF:ReportRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, report] +# @PURPOSE: Persist immutable compliance reports and support report lookup by run and candidate. +# @LAYER: Infra +# @INVARIANT: Completed reports remain immutable. +# [/DEF:ReportRepositoryModule] + +# [DEF:ApprovalRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, approval] +# @PURPOSE: Persist immutable approval decisions and query latest decision state. +# @LAYER: Infra +# @INVARIANT: Approval decisions are historical facts, not mutable flags. +# [/DEF:ApprovalRepositoryModule] + +# [DEF:PublicationRepositoryModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, repository, publication] +# @PURPOSE: Persist publication and revocation records. +# @LAYER: Infra +# @INVARIANT: Publication history is append-only. +# [/DEF:PublicationRepositoryModule] + +# [DEF:TrustedPolicyStoreModule:Module] +# @TIER: STANDARD +# @SEMANTICS: [clean_release, trusted_store, policy] +# @PURPOSE: Abstract the trusted read-only source of policies and source registries. +# @LAYER: Infra +# @INVARIANT: Store reads are side-effect free for clean release operations. +# [/DEF:TrustedPolicyStoreModule] + +--- + +## Backend REST API Contract + +# [DEF:CleanReleaseApiContract:Module] +# @TIER: CRITICAL +# @SEMANTICS: [api, clean_release, async, dto] +# @PURPOSE: Define HTTP contract for candidate lifecycle, manifests, compliance runs, approvals and publications. +# @LAYER: Interface +# @RELATION: DEPENDS_ON -> [DEF:CleanReleaseFacadeModule] +# @RELATION: IMPLEMENTS -> [DEF:Std:API_FastAPI] +# @INVARIANT: Long-running compliance execution endpoints are non-blocking and machine-readable. +# @PRE: Request is authenticated and actor context is available. +# @POST: Mutation endpoints return canonical DTOs or explicit validation/system errors. +# @POST: Compliance run creation returns run_id and task_id without waiting for final completion; latest manifest may be resolved server-side when not explicitly provided. +# @TEST_CONTRACT: CleanReleaseApiContract -> {Input: HTTP request, Output: JSON DTO or machine-readable error} +# @TEST_FIXTURE: api_candidate_create -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","version":"1.2.0"} +# @TEST_EDGE: invalid_transition_http -> 409 conflict +# @TEST_EDGE: missing_candidate_http -> 404 not found +# @TEST_EDGE: invalid_input_http -> 422 validation error +# @TEST_EDGE: reject_without_passed_report_http -> 409 conflict +# @TEST_INVARIANT: api_contract_stability -> VERIFIED_BY: [invalid_transition_http, missing_candidate_http, invalid_input_http, reject_without_passed_report_http] +# [/DEF:CleanReleaseApiContract] + +--- + +## Backend CLI Contract + +# [DEF:CleanReleaseCliContract:Module] +# @TIER: CRITICAL +# @SEMANTICS: [cli, clean_release, headless] +# @PURPOSE: Provide headless command-line access to candidate lifecycle, compliance, approval, publication and demo seed/reset flows. +# @LAYER: Interface +# @RELATION: DEPENDS_ON -> [DEF:CleanReleaseFacadeModule] +# @INVARIANT: CLI exit codes distinguish passed, blocked, invalid input and system error outcomes. +# @PRE: User provides explicit command arguments for business actions. +# @POST: CLI emits human-readable output by default and JSON output when requested. +# @TEST_CONTRACT: CleanReleaseCliContract -> {Input: argv, Output: stdout/stderr + exit code} +# @TEST_FIXTURE: cli_run_json -> INLINE_JSON {"argv":["clean-release","compliance","run","--candidate-id","2026.03.09-rc1","--json"]} +# @TEST_EDGE: cli_missing_manifest -> exit code 2 +# @TEST_EDGE: cli_blocked_run -> exit code 1 +# @TEST_EDGE: cli_system_error -> exit code 3 +# @TEST_INVARIANT: cli_headless_integrity -> VERIFIED_BY: [cli_missing_manifest, cli_blocked_run, cli_system_error] +# [/DEF:CleanReleaseCliContract] + +--- + +## TUI Thin Client Contract + + +/** + * @TIER: CRITICAL + * @SEMANTICS: [tui, thin_client, operator, live_status] + * @PURPOSE: Render current clean release overview and trigger facade actions without owning business logic. + * @LAYER: UI + * @RELATION: DEPENDS_ON -> [DEF:CleanReleaseFacadeModule] + * @INVARIANT: TUI never constructs policy, registry, manifest or fake run state locally. + * @PRE: Running terminal has a valid TTY; candidate context is resolvable. + * @POST: Operator can build manifest, run compliance, approve and publish only when transitions are valid. + * @UX_STATE: Loading -> Candidate overview is refreshing and action keys are temporarily disabled. + * @UX_STATE: Ready -> Candidate summary, latest manifest, latest run and latest report are visible. + * @UX_STATE: Running -> Current stage and task/run progress are visible from real task events. + * @UX_STATE: Blocked -> Violations list and blocking reason are visually dominant; approve/publish disabled. + * @UX_STATE: Error -> Failure reason is explicit and no hidden retry occurs. + * @UX_FEEDBACK: F5/F6/F8/F9 actions show immediate command acknowledgment and then refresh from persisted DTOs. + * @UX_RECOVERY: Operator can refresh, inspect violations, rebuild manifest or rerun compliance after fixing candidate inputs. + * @TEST_CONTRACT: CleanReleaseTuiApp -> {Input: keypress + facade DTOs, Output: rendered state + triggered facade action} + * @TEST_FIXTURE: tui_ready_candidate -> INLINE_JSON {"candidate_id":"2026.03.09-rc1","status":"MANIFEST_BUILT"} + * @TEST_EDGE: no_tty_environment -> refuse startup and redirect to CLI + * @TEST_EDGE: missing_manifest_on_F5 -> inline blocking message + * @TEST_EDGE: blocked_report_on_F8 -> approve action disabled + * @TEST_INVARIANT: tui_thin_client_boundary -> VERIFIED_BY: [no_tty_environment, missing_manifest_on_F5, blocked_report_on_F8] + */ + + +--- + +## Contract Usage Simulation (Key Scenario) + +Scenario traced: operator runs compliance from TUI after building a manifest. + +1. `CleanReleaseTuiApp` requests `CandidateOverviewDTO` from `CleanReleaseFacadeModule`. +2. Operator presses `F5`. +3. `CleanReleaseFacadeModule` calls `ComplianceExecutionServiceModule.request_run(...)`. +4. `PolicyResolutionServiceModule` resolves trusted snapshots. +5. `ComplianceExecutionServiceModule` creates a run, binds it to `TaskManagerModule`, returns `run_id` and `task_id` immediately. +6. `StagePipelineModule` emits ordered stage results and violations. +7. `AuditServiceModule` persists append-only events. +8. `ReportRepositoryModule` persists immutable final report on terminal completion. +9. TUI refreshes and renders persisted DTOs without local recomputation. + +Continuity check: no interface mismatch found across facade, services and thin-client rendering path. diff --git a/specs/025-clean-release-compliance/data-model.md b/specs/025-clean-release-compliance/data-model.md new file mode 100644 index 00000000..c53f0f0c --- /dev/null +++ b/specs/025-clean-release-compliance/data-model.md @@ -0,0 +1,359 @@ +# Data Model: Clean Release Compliance Subsystem Redesign + +**Feature**: [`025-clean-release-compliance`](specs/025-clean-release-compliance) +**Spec**: [`spec.md`](specs/025-clean-release-compliance/spec.md) +**Research**: [`research.md`](specs/025-clean-release-compliance/research.md) + +## 1. Entity: ReleaseCandidate + +Represents the release unit that is being prepared, checked, approved and published. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Stable candidate identifier. | +| version | string | Yes | Release version label. | +| source_snapshot_ref | string | Yes | Reference to code/source snapshot used to create candidate. | +| build_id | string | No | Upstream build or pipeline identifier. | +| created_at | datetime | Yes | Candidate creation timestamp. | +| created_by | string | Yes | Actor that created the candidate. | +| provenance_ref | string | No | Optional provenance or attestation reference. | +| status | enum | Yes | Current lifecycle state. | + +### Validation Rules + +- `id`, `version`, `source_snapshot_ref`, `created_at`, `created_by`, `status` must be present. +- `status` must be one of: `DRAFT`, `PREPARED`, `MANIFEST_BUILT`, `CHECK_PENDING`, `CHECK_RUNNING`, `CHECK_PASSED`, `CHECK_BLOCKED`, `CHECK_ERROR`, `APPROVED`, `PUBLISHED`, `REVOKED`. +- Terminal publication evidence cannot exist without a valid candidate. + +### Lifecycle / State Transitions + +- `DRAFT -> PREPARED` +- `PREPARED -> MANIFEST_BUILT` +- `MANIFEST_BUILT -> CHECK_PENDING` +- `CHECK_PENDING -> CHECK_RUNNING` +- `CHECK_RUNNING -> CHECK_PASSED` +- `CHECK_RUNNING -> CHECK_BLOCKED` +- `CHECK_RUNNING -> CHECK_ERROR` +- `CHECK_PASSED -> APPROVED` +- `APPROVED -> PUBLISHED` +- `PUBLISHED -> REVOKED` + +--- + +## 2. Entity: CandidateArtifact + +Represents one artifact associated with a release candidate. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Stable artifact identifier. | +| candidate_id | string | Yes | Owning release candidate. | +| path | string | Yes | Artifact path or logical location. | +| sha256 | string | Yes | Artifact checksum. | +| size | integer | Yes | Artifact size in bytes. | +| detected_category | string | No | Category determined by system or pipeline. | +| declared_category | string | No | Category declared by external input. | +| source_uri | string | No | Artifact source URI if known. | +| source_host | string | No | Parsed source host if known. | +| metadata | object | Yes | Additional attributes. | + +### Validation Rules + +- `sha256` must be present and stable for the artifact content. +- `size >= 0`. +- `declared_category` and `detected_category` may differ, but the difference must remain observable. + +--- + +## 3. Entity: DistributionManifest + +Immutable snapshot describing the candidate payload selected for distribution. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Stable manifest identifier. | +| candidate_id | string | Yes | Owning release candidate. | +| manifest_version | integer | Yes | Monotonic manifest version per candidate. | +| manifest_digest | string | Yes | Overall digest of manifest content. | +| artifacts_digest | string | Yes | Digest over artifact selection. | +| created_at | datetime | Yes | Manifest creation time. | +| created_by | string | Yes | Actor that built the manifest. | +| source_snapshot_ref | string | Yes | Source snapshot bound to the manifest. | +| content_json | object | Yes | Serialized immutable manifest content. | +| immutable | boolean | Yes | Must be `true` after creation. | + +### Validation Rules + +- `manifest_version >= 1` and strictly increases for a candidate. +- Existing manifest content cannot be overwritten. +- `manifest_digest` must uniquely reflect `content_json`. + +--- + +## 4. Entity: CleanPolicySnapshot + +Immutable policy snapshot used to evaluate a run. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Snapshot identifier. | +| policy_id | string | Yes | Logical policy identifier in trusted store. | +| policy_version | string | Yes | Trusted policy version. | +| created_at | datetime | Yes | Snapshot creation time. | +| content_json | object | Yes | Frozen policy content. | +| registry_snapshot_id | string | Yes | Bound source registry snapshot. | +| immutable | boolean | Yes | Must be `true`. | + +### Validation Rules + +- Snapshot must reference a valid registry snapshot. +- UI/env input cannot mutate `content_json` after creation. + +--- + +## 5. Entity: SourceRegistrySnapshot + +Immutable registry snapshot for allowed sources and schemes. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Snapshot identifier. | +| registry_id | string | Yes | Logical trusted registry identifier. | +| registry_version | string | Yes | Trusted registry version. | +| created_at | datetime | Yes | Snapshot creation time. | +| allowed_hosts | array[string] | Yes | Allowed hosts or host patterns. | +| allowed_schemes | array[string] | Yes | Allowed URL schemes. | +| allowed_source_types | array[string] | Yes | Allowed source type labels. | +| immutable | boolean | Yes | Must be `true`. | + +--- + +## 6. Entity: ComplianceRun + +Operational record for one compliance execution request. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Run identifier. | +| candidate_id | string | Yes | Candidate being checked. | +| manifest_id | string | Yes | Manifest used for the run. | +| manifest_digest | string | Yes | Manifest digest copied at request time. | +| policy_snapshot_id | string | Yes | Policy snapshot used. | +| registry_snapshot_id | string | Yes | Registry snapshot used. | +| requested_by | string | Yes | Actor that requested the run. | +| requested_at | datetime | Yes | Request time. | +| started_at | datetime | No | Execution start time. | +| finished_at | datetime | No | Execution finish time. | +| status | enum | Yes | Execution status. | +| final_status | enum | No | Final compliance result. | +| failure_reason | string | No | System or validation failure summary. | +| task_id | string | No | Linked async task identifier. | + +### Validation Rules + +- `status` must be one of `PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`, `CANCELLED`. +- `final_status`, when present, must be one of `PASSED`, `BLOCKED`, `ERROR`. +- `task_id` is mutable only until execution binding is established. + +--- + +## 7. Entity: ComplianceStageRun + +Stage-level execution record inside one run. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Stage run identifier. | +| run_id | string | Yes | Owning compliance run. | +| stage_name | string | Yes | Canonical stage name. | +| status | string | Yes | Stage execution status. | +| started_at | datetime | No | Stage start time. | +| finished_at | datetime | No | Stage finish time. | +| decision | string | No | `PASSED`, `BLOCKED`, or `ERROR`. | +| details_json | object | Yes | Structured stage details. | + +--- + +## 8. Entity: ComplianceViolation + +Violation produced by one stage within one run. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Violation identifier. | +| run_id | string | Yes | Owning compliance run. | +| stage_name | string | Yes | Stage that detected the issue. | +| code | string | Yes | Canonical violation code. | +| severity | string | Yes | Severity label. | +| artifact_path | string | No | Related artifact path. | +| artifact_sha256 | string | No | Related artifact checksum. | +| message | string | Yes | Human-readable explanation. | +| evidence_json | object | Yes | Structured evidence. | + +### Validation Rules + +- Violations cannot be deleted in real mode. +- `code`, `severity`, `message` must be present. + +--- + +## 9. Entity: ComplianceReport + +Immutable result derived from a completed run. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Report identifier. | +| run_id | string | Yes | Source compliance run. | +| candidate_id | string | Yes | Candidate checked by this run. | +| final_status | string | Yes | Final outcome. | +| summary_json | object | Yes | Structured summary of stages and violations. | +| generated_at | datetime | Yes | Report generation time. | +| immutable | boolean | Yes | Must be `true`. | + +--- + +## 10. Entity: ApprovalDecision + +Approval or rejection bound to a candidate and report. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Decision identifier. | +| candidate_id | string | Yes | Candidate being decided. | +| report_id | string | Yes | Report used as approval basis. | +| decision | string | Yes | `APPROVED` or `REJECTED`. | +| decided_by | string | Yes | Actor making the decision. | +| decided_at | datetime | Yes | Decision timestamp. | +| comment | string | No | Optional operator note. | + +--- + +## 11. Entity: PublicationRecord + +Publication or revocation record bound to a candidate. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| id | string | Yes | Publication identifier. | +| candidate_id | string | Yes | Published candidate. | +| report_id | string | Yes | Approved report used as basis. | +| published_by | string | Yes | Publishing actor. | +| published_at | datetime | Yes | Publish time. | +| target_channel | string | Yes | Target release channel. | +| publication_ref | string | No | External publish reference. | +| status | string | Yes | Publication status label. | + +--- + +## 12. Entity: CandidateOverviewDTO + +Read model used by CLI, TUI and API to show current candidate state. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| candidate_id | string | Yes | Candidate identifier. | +| version | string | Yes | Candidate version. | +| source_snapshot_ref | string | Yes | Source snapshot ref. | +| status | string | Yes | Current lifecycle status. | +| latest_manifest_id | string | No | Latest manifest identifier. | +| latest_manifest_digest | string | No | Latest manifest digest. | +| latest_run_id | string | No | Latest run identifier. | +| latest_run_status | string | No | Latest run execution status. | +| latest_report_id | string | No | Latest report identifier. | +| latest_report_final_status | string | No | Latest report final status. | +| latest_policy_snapshot_id | string | No | Latest policy snapshot identifier. | +| latest_policy_version | string | No | Latest policy version used for latest run. | +| latest_registry_snapshot_id | string | No | Latest registry snapshot identifier. | +| latest_registry_version | string | No | Latest registry version used for latest run. | +| latest_approval_decision | string | No | Latest approval decision affecting publication gate. | +| latest_publication_id | string | No | Latest publication identifier. | +| latest_publication_status | string | No | Latest publication status. | + +--- + +## 13. Entity: ComplianceRunDTO + +Read model for run status tracking. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| run_id | string | Yes | Run identifier. | +| candidate_id | string | Yes | Candidate identifier. | +| status | string | Yes | Run execution status. | +| final_status | string | No | Final compliance result. | +| report_id | string | No | Final report identifier. | +| task_id | string | Yes | Linked task identifier. | + +--- + +## 14. Entity: ReportDTO + +Compact report view used by interfaces. + +### Fields + +| Field | Type | Required | Description | +|---|---|---|---| +| report_id | string | Yes | Report identifier. | +| candidate_id | string | Yes | Candidate identifier. | +| final_status | string | Yes | Final report result. | +| policy_version | string | Yes | Policy version used for the report. | +| manifest_digest | string | Yes | Manifest digest used for the run. | +| violation_count | integer | Yes | Number of violations. | +| generated_at | datetime | Yes | Report generation time. | + +--- + +## 15. Relationships + +- `ReleaseCandidate 1 -> N CandidateArtifact` +- `ReleaseCandidate 1 -> N DistributionManifest` +- `ReleaseCandidate 1 -> N ComplianceRun` +- `ComplianceRun 1 -> N ComplianceStageRun` +- `ComplianceRun 1 -> N ComplianceViolation` +- `ComplianceRun 1 -> 1 ComplianceReport` (for terminal runs that produce a report) +- `ComplianceReport 1 -> N ApprovalDecision` (history allowed, latest valid decision governs) +- `ReleaseCandidate 1 -> N PublicationRecord` +- `CleanPolicySnapshot 1 -> 1 SourceRegistrySnapshot` + +## 16. Invariants + +- Policy, registry, manifest, report, approval and publication snapshots are immutable after creation. +- Real mode compliance evidence is append-only and not user-deletable. +- `APPROVED` requires a valid `PASSED` report. +- `PUBLISHED` requires a valid approval. +- `REJECTED` is modeled as an immutable latest approval decision, not as a separate candidate lifecycle state. +- Demo namespace never shares identifiers or historical storage with real mode. + +## 17. Scale Assumptions + +- Candidate histories may accumulate multiple manifests and multiple compliance runs per release. +- A single run may produce many violations and stage details, so read models should not require loading all artifacts for each overview request. +- Most interface reads use latest-only summary views, while audit and compliance screens require full history retrieval. diff --git a/specs/025-clean-release-compliance/plan.md b/specs/025-clean-release-compliance/plan.md new file mode 100644 index 00000000..955f6b6f --- /dev/null +++ b/specs/025-clean-release-compliance/plan.md @@ -0,0 +1,163 @@ +# Implementation Plan: Clean Release Compliance Subsystem Redesign + +**Branch**: `025-clean-release-compliance` | **Date**: 2026-03-09 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from [`/specs/025-clean-release-compliance/spec.md`](./spec.md) + +## Summary + +Перевести текущую clean-release подсистему из TUI-first orchestration в API/CLI-first release/compliance subsystem с четырьмя жёстко разделёнными слоями: +1. domain model для candidate, manifest, policy snapshot, run, report, approval, publication; +2. application services для candidate preparation, manifest build, policy resolution, compliance execution, approval, publication и audit; +3. infrastructure layer для repositories, trusted policy access, artifact storage, audit storage и task execution; +4. thin interfaces для CLI, TUI, REST API и будущего Web UI. + +Ключевой результат: TUI перестаёт быть местом, где живёт release logic. Все критические действия выполняются единым facade/application layer, compliance evidence становится immutable/append-only, а long-running runs интегрируются с существующим TaskManager. + +## Technical Context + +**Language/Version**: Python 3.9+ for backend services, CLI, API and TUI +**Primary Dependencies**: FastAPI, Pydantic models, existing `TaskManager`, existing reports service patterns, repository adapters, curses-compatible TUI runtime +**Storage**: PostgreSQL for metadata and snapshots, filesystem/object storage for artifacts and persisted reports, trusted policy/registry store (read-only source) +**Testing**: pytest unit/integration/contract suites, CLI smoke tests, API contract checks, TUI facade smoke tests +**Target Platform**: Linux server and CI pipeline in enterprise environment +**Project Type**: Backend web service with operational CLI/TUI interfaces +**Performance Goals**: +- request to start compliance returns run/task identifiers in <= 2 seconds for a typical candidate; +- candidate overview and status reads return in <= 1 second for the latest candidate history; +- final report becomes available immediately after terminal run finalization. +**Constraints**: +- policy and registry are trusted read-only inputs, never sourced from TUI/env bootstrap payloads; +- trusted policy store location, active profile selection, mode switching and storage wiring must be resolved through existing `ConfigManager` access patterns rather than ad hoc constants or raw env reads; +- long-running compliance execution must use `TaskManager` and non-blocking API behavior; +- real mode audit history and compliance evidence are append-only; +- TUI must remain usable for operators but cannot contain hidden business logic; +- migration should be incremental because existing code already exposes `clean_release` routes, services and a TUI entrypoint. +**Scale/Scope**: +- tens to hundreds of release candidates per month; +- each candidate may include thousands of artifacts; +- multiple compliance runs and reports may exist for one candidate; +- redesign touches backend domain, API, CLI, TUI and operational documentation. + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +1. **Semantic Protocol Compliance**: All new modules in this plan are defined via explicit contracts in [`contracts/modules.md`](./contracts/modules.md). Code implementation must follow `[DEF]`, `@PRE`, `@POST`, testing tags and closing anchors. Status: PASS. +2. **Modular Architecture**: Business logic is moved into `backend/src/services/clean_release/` services and repository adapters; interfaces remain thin. Status: PASS. +3. **Independent Testability**: Spec defines independent tests for each user story; tasks will preserve isolated validation paths. Status: PASS. +4. **Asynchronous Execution**: Compliance run execution is explicitly mapped to `TaskManager`, and API launch endpoints are non-blocking. Status: PASS. +5. **Config Discipline**: Policy and registry sources are moved out of ad hoc env/bootstrap input into trusted resolution services. Status: PASS with migration note that existing env-based flows must be deprecated, not silently retained. +6. **Centralized Config**: Trusted store endpoints, mode/profile selection and storage wiring must reuse `ConfigManager` / `get_config_manager()` patterns required by repository constitution. Status: PASS with explicit implementation follow-up in foundational tasks. + +## Project Structure + +### Documentation (this feature) + +```text +specs/025-clean-release-compliance/ +├── spec.md +├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── ux_reference.md +├── contracts/ +│ ├── modules.md +│ ├── clean-release-api.openapi.yaml +│ └── cli.md +├── checklists/ +│ └── requirements.md +└── tasks.md +``` + +### Source Code (repository root) + +```text +backend/ +├── src/ +│ ├── api/routes/clean_release.py +│ ├── dependencies.py +│ ├── models/clean_release.py +│ ├── scripts/ +│ │ ├── clean_release_cli.py +│ │ └── clean_release_tui.py +│ └── services/clean_release/ +│ ├── __init__.py +│ ├── facade.py +│ ├── enums.py +│ ├── exceptions.py +│ ├── dto.py +│ ├── mappers.py +│ ├── candidate_service.py +│ ├── manifest_service.py +│ ├── policy_resolution_service.py +│ ├── compliance_execution_service.py +│ ├── approval_service.py +│ ├── publication_service.py +│ ├── audit_service.py +│ ├── demo_data_service.py +│ ├── stages/ +│ │ ├── base.py +│ │ ├── data_purity.py +│ │ ├── internal_sources_only.py +│ │ ├── no_external_endpoints.py +│ │ └── manifest_consistency.py +│ └── repositories/ +│ ├── candidate_repository.py +│ ├── artifact_repository.py +│ ├── manifest_repository.py +│ ├── policy_repository.py +│ ├── compliance_repository.py +│ ├── report_repository.py +│ ├── approval_repository.py +│ ├── publication_repository.py +│ └── audit_repository.py +└── tests/ + ├── api/routes/ + ├── scripts/ + └── services/clean_release/ +``` + +**Structure Decision**: Сохраняем существующую backend-centric структуру проекта и расширяем текущий пакет `backend/src/services/clean_release/` вместо выделения нового top-level приложения. Это уменьшает migration risk и позволяет поэтапно заменить текущие `preparation_service`, `manifest_builder`, `compliance_orchestrator` и `clean_release_tui.py` на фасадную архитектуру. + +## Phase 0 Research Scope + +Research resolved the main architectural questions and produced decisions in [`research.md`](./research.md): +- trust model for policy and registry; +- immutable snapshot strategy; +- lifecycle/state machine design; +- TaskManager integration strategy; +- interface split between CLI/API/TUI; +- repository decomposition and migration approach; +- demo mode isolation. + +## Phase 1 Design Outputs + +Phase 1 produces: +- [`data-model.md`](./data-model.md) with canonical entities, states and relationships; +- [`contracts/modules.md`](./contracts/modules.md) with module-level contracts for new services and interfaces; +- [`contracts/clean-release-api.openapi.yaml`](./contracts/clean-release-api.openapi.yaml) and [`contracts/cli.md`](./contracts/cli.md) for programmatic interfaces; +- [`ux_reference.md`](./ux_reference.md) for thin-client TUI/CLI behavior; +- [`quickstart.md`](./quickstart.md) for implementation and validation scenarios. + +## Post-Design Constitution Re-Check + +1. **No interface-owned business logic**: Preserved by facade + application services. PASS. +2. **Async long-running work**: Preserved by explicit `ComplianceExecutionService -> TaskManager` mapping. PASS. +3. **Independent testability**: Preserved via user-story tasks and dedicated contract tests. PASS. +4. **Config discipline and trust boundaries**: Preserved by read-only policy resolution service and immutable snapshots. PASS. + +## Implementation Risks To Control + +- Existing `clean_release_tui.py` currently owns preparation/build/run logic and must be thinned without breaking operator workflow. +- Existing repository and model naming may conflict with the redesigned entity boundaries; migration shims may be needed. +- Existing `/api/clean-release/checks*` endpoints may require compatibility strategy while introducing candidate/manifest/run-oriented endpoints. +- Existing `.clean-release.yaml` driven flow from `023-clean-repo-enterprise` conflicts with the new trusted policy-store model and must be deliberately deprecated or scoped. + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| Multiple service modules instead of one orchestrator | Trust boundaries, immutability and lifecycle rules need explicit ownership | One orchestrator keeps the current blending of UI, policy, execution and persistence concerns | +| Separate repositories/facade layer | Needed for append-only evidence and independent interfaces | Single universal repository would keep ambiguous ownership and weak contracts | +| TaskManager integration for runs | Constitution requires async lifecycle and observability | Synchronous API/TUI execution would violate non-blocking execution requirements | diff --git a/specs/025-clean-release-compliance/quickstart.md b/specs/025-clean-release-compliance/quickstart.md new file mode 100644 index 00000000..485c9b69 --- /dev/null +++ b/specs/025-clean-release-compliance/quickstart.md @@ -0,0 +1,117 @@ +# Quickstart: Clean Release Compliance Subsystem Redesign + +## Purpose + +Use this package to implement and validate the redesigned clean release subsystem defined in: +- Spec: [`spec.md`](specs/025-clean-release-compliance/spec.md) +- Plan: [`plan.md`](specs/025-clean-release-compliance/plan.md) +- UX reference: [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md) +- Data model: [`data-model.md`](specs/025-clean-release-compliance/data-model.md) +- Contracts: [`contracts/modules.md`](specs/025-clean-release-compliance/contracts/modules.md), [`contracts/clean-release-api.openapi.yaml`](specs/025-clean-release-compliance/contracts/clean-release-api.openapi.yaml), [`contracts/cli.md`](specs/025-clean-release-compliance/contracts/cli.md) + +## 1) Backend implementation flow + +1. Introduce canonical lifecycle/domain entities and state machine guards. +2. Split current clean release logic into dedicated application services and repository interfaces. +3. Resolve policy and registry from trusted stores into immutable snapshots. +4. Move compliance execution to `TaskManager` and persist stage events, violations and reports. +5. Expose candidate/manifest/compliance/release operations through one facade. +6. Rebuild API and CLI on top of the facade. +7. Refactor TUI into a read/trigger-only client. + +## 2) Headless operator flow target + +### Candidate registration and artifact import + +```bash +clean-release candidate register \ + --candidate-id 2026.03.09-rc1 \ + --version 1.2.0 \ + --source-snapshot v1.2.0-rc1 \ + --actor release-bot + +clean-release candidate import-artifacts \ + --candidate-id 2026.03.09-rc1 \ + --input artifacts.json \ + --actor release-bot +``` + +### Manifest and compliance + +```bash +clean-release manifest build --candidate-id 2026.03.09-rc1 --actor release-bot +clean-release compliance run --candidate-id 2026.03.09-rc1 --manifest-id man-001 --actor release-bot --json +clean-release compliance status --run-id run-001 --json +clean-release compliance report --run-id run-001 --json +``` + +### Approval and publication + +```bash +clean-release release approve --candidate-id 2026.03.09-rc1 --report-id rpt-001 --actor release-owner +clean-release release publish --candidate-id 2026.03.09-rc1 --report-id rpt-001 --channel prod --actor release-owner +``` + +## 3) REST API smoke checklist + +- `POST /api/clean-release/candidates` creates candidate and returns overview DTO. +- `POST /api/clean-release/candidates/{candidate_id}/artifacts/import` persists artifacts without triggering compliance. +- `POST /api/clean-release/candidates/{candidate_id}/manifest` creates a new immutable manifest version. +- `POST /api/clean-release/candidates/{candidate_id}/compliance-runs` returns `202` with `run_id` and `task_id`. +- `GET /api/clean-release/compliance-runs/{run_id}` returns live execution status. +- `GET /api/clean-release/compliance-runs/{run_id}/stages` returns ordered stage results. +- `GET /api/clean-release/compliance-runs/{run_id}/violations` returns append-only violations. +- `GET /api/clean-release/compliance-runs/{run_id}/report` returns immutable final report for completed runs. +- `POST /api/clean-release/candidates/{candidate_id}/approve` enforces passed-report gate. +- `POST /api/clean-release/candidates/{candidate_id}/publish` enforces approval gate. + +## 4) UX conformance checks (must pass) + +- TUI shows latest candidate, latest manifest, latest run and latest report without mutating them. +- `F6` builds manifest explicitly; `F5` never auto-builds hidden manifest. +- `F8` is disabled or blocked with explicit reason unless candidate is in `CHECK_PASSED`. +- `F9` is disabled or blocked with explicit reason unless candidate is `APPROVED`. +- Running state is sourced from real run/task progress, not simulated local state. +- Non-TTY startup redirects the operator to CLI/API path instead of headless TUI mode. +- Demo mode actions never expose or modify real-mode history. + +## 5) Contract checks (must pass) + +- Domain transitions conform to the lifecycle in [`data-model.md`](specs/025-clean-release-compliance/data-model.md). +- API payloads conform to [`clean-release-api.openapi.yaml`](specs/025-clean-release-compliance/contracts/clean-release-api.openapi.yaml). +- CLI commands and exit codes conform to [`cli.md`](specs/025-clean-release-compliance/contracts/cli.md). +- Immutable entities are never updated in place. +- Real-mode runs, reports, violations and audit events remain append-only. + +## 6) Suggested validation commands + +Backend targeted tests: +```bash +cd backend && .venv/bin/python3 -m pytest tests/services/clean_release tests/api/routes -k clean_release -q +``` + +CLI smoke tests: +```bash +cd backend && .venv/bin/python3 -m pytest tests/scripts/test_clean_release_cli.py -q +``` + +TUI thin-client smoke tests: +```bash +cd backend && .venv/bin/python3 -m pytest tests/scripts/test_clean_release_tui_v2.py -q +``` + +## 7) Migration checkpoints + +1. Old TUI logic no longer directly prepares candidates, builds manifests or finalizes runs. +2. Legacy `/api/clean-release/checks*` entrypoints have explicit compatibility or deprecation behavior. +3. Trusted policy resolution no longer depends on UI/env bootstrap payloads. +4. Compliance run state is visible through both TaskManager and clean-release run records. +5. Demo namespace and real namespace are visibly isolated. + +## 8) Done criteria for planning handoff + +- All planning artifacts exist and are internally consistent. +- State machine, trust boundaries and immutable evidence model are defined. +- CLI and REST contracts are stable enough for parallel implementation. +- TUI UX reference is explicitly thin-client only. +- Ready to decompose into executable work items via [`tasks.md`](specs/025-clean-release-compliance/tasks.md). diff --git a/specs/025-clean-release-compliance/research.md b/specs/025-clean-release-compliance/research.md new file mode 100644 index 00000000..3bb7c5f8 --- /dev/null +++ b/specs/025-clean-release-compliance/research.md @@ -0,0 +1,125 @@ +# Phase 0 Research: Clean Release Compliance Subsystem Redesign + +## Decision 1: The subsystem becomes API/CLI-first and TUI becomes a thin client + +**Decision** +Primary release operations are owned by application services and exposed through CLI and HTTP API first. TUI is retained only as a thin operator interface that reads state and triggers actions through the same facade. + +**Rationale** +The current implementation mixes UI state with preparation, manifest creation and compliance execution. This blocks automation and makes behavior depend on the interface used. + +**Alternatives considered** +- Keep TUI as primary orchestrator and add wrappers around it: rejected because it preserves hidden business logic inside the interface. +- Remove TUI entirely: rejected because operators still need an interactive console flow in enterprise environments. + +--- + +## Decision 2: Policy and registry are trusted snapshots, never runtime UI/env payloads + +**Decision** +Policy and source registry are resolved by a dedicated read-only resolution service from trusted stores, then frozen into immutable snapshots for each compliance run. + +**Rationale** +The redesign explicitly separates trusted and untrusted inputs. Candidate input, artifacts JSON and operator choices are not allowed to define policy contents or final report outcomes. + +**Alternatives considered** +- Continue using `.clean-release.yaml` and env bootstrap as policy source: rejected because it violates the new trust model. +- Let TUI construct policy in demo and real mode differently: rejected because it breaks evidence integrity and reproducibility. + +--- + +## Decision 3: Manifest, report and snapshots are immutable; run history is append-only + +**Decision** +`DistributionManifest`, `CleanPolicySnapshot`, `SourceRegistrySnapshot`, `ComplianceReport`, `ApprovalDecision` and `PublicationRecord` are immutable. `ComplianceRun`, `ComplianceStageRun`, `ComplianceViolation` and audit log are append-only once created; only non-terminal run fields may progress during execution. + +**Rationale** +The main value of the subsystem is evidence integrity. Mutable manifest/report records make audit and publication safety unverifiable. + +**Alternatives considered** +- Update manifest/report in place: rejected because historical evidence would be lost. +- Allow deleting old runs to keep storage small: rejected because real mode must preserve evidence. + +--- + +## Decision 4: Release lifecycle is modeled as an explicit state machine + +**Decision** +The candidate lifecycle is formalized as `DRAFT -> PREPARED -> MANIFEST_BUILT -> CHECK_PENDING -> CHECK_RUNNING -> CHECK_PASSED|CHECK_BLOCKED|CHECK_ERROR -> APPROVED -> PUBLISHED -> REVOKED`, with hard guards on forbidden transitions. + +**Rationale** +Current logic spreads status changes across TUI and orchestration code. A formal state machine makes approval/publication gating deterministic and testable. + +**Alternatives considered** +- Keep loose status updates per module: rejected because it produces hidden invalid states. +- Collapse all states into a smaller set: rejected because manifest, check and approval stages need separate audit visibility. + +--- + +## Decision 5: Compliance execution is a pluggable stage pipeline integrated with TaskManager + +**Decision** +Each compliance run becomes a `TaskManager` task. The run stores lifecycle metadata while stage logs are emitted as task logs or structured sub-events. The pipeline remains pluggable with stage-specific decisions and violations. + +**Rationale** +The repository already has a mature async task lifecycle and reporting patterns. Reusing it reduces duplicated orchestration infrastructure and aligns with repository constitution. + +**Alternatives considered** +- Keep synchronous orchestrator execution: rejected due to non-blocking API requirements. +- Build a second custom task subsystem inside clean release: rejected as redundant and harder to observe. + +--- + +## Decision 6: Interfaces are split into CLI, REST API and thin TUI over one facade + +**Decision** +A single `CleanReleaseFacade` exposes use cases for candidate overview, manifest build, compliance run, approval and publication. CLI, API and TUI all call the facade. Headless mode belongs to CLI/API only. + +**Rationale** +A facade keeps interface code thin and prevents re-implementing business rules per entrypoint. + +**Alternatives considered** +- Let each interface call lower-level services directly: rejected because state validation and DTO assembly would drift. +- Keep a headless branch inside TUI: rejected because headless is not a UI concern. + +--- + +## Decision 7: Repositories are decomposed by responsibility, even if exposed through one internal facade + +**Decision** +Persistence is split by bounded responsibility: candidate, artifacts, manifest, policy, compliance run, report, approval, publication and audit. A convenience facade may exist, but ownership remains explicit. + +**Rationale** +The current `CleanReleaseRepository` is too broad for the redesigned evidence model. Explicit repository boundaries make append-only and immutable behavior easier to enforce. + +**Alternatives considered** +- Keep one universal repository class: rejected because contracts stay ambiguous. +- Persist everything only through TaskManager: rejected because domain entities need direct retrieval independently of task history. + +--- + +## Decision 8: Demo mode is preserved but isolated by namespace + +**Decision** +Demo mode is handled by a dedicated demo service and isolated storage namespace. Demo runs, policies, candidates and reports never share identifiers or history with real mode. + +**Rationale** +Demo mode remains useful for operator training, but it must not contaminate real compliance evidence. + +**Alternatives considered** +- Simulate demo behavior inside real storage: rejected because it risks false evidence and operator confusion. +- Drop demo mode entirely: rejected because it removes a safe training path. + +--- + +## Decision 9: Migration proceeds incrementally, starting by extracting services out of TUI + +**Decision** +Migration starts by extracting build/run logic into new services/facade, then removes env-driven policy injection, then introduces immutable snapshots, then adds CLI/API contracts, and only after that thins the TUI. + +**Rationale** +The current codebase already has working routes, models and tests. A big-bang rewrite would create unnecessary integration risk. + +**Alternatives considered** +- Rewrite the whole subsystem at once: rejected because it is harder to validate incrementally. +- Patch TUI only: rejected because it does not solve the architectural problem. diff --git a/specs/025-clean-release-compliance/spec.md b/specs/025-clean-release-compliance/spec.md new file mode 100644 index 00000000..f50c1f8d --- /dev/null +++ b/specs/025-clean-release-compliance/spec.md @@ -0,0 +1,153 @@ +# Feature Specification: Clean Release Compliance Subsystem Redesign + +**Feature Branch**: `025-clean-release-compliance` +**Created**: 2026-03-09 +**Status**: Draft +**Input**: User description: "Редизайн текущего clean release TUI-checker в нормальную API/CLI-first release/compliance subsystem с разделением domain/application/infrastructure/interfaces, immutable snapshots, append-only compliance evidence, approval/publication flow, thin TUI client, REST API и интеграцией с TaskManager." + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Headless candidate and manifest lifecycle (Priority: P1) + +Как release-менеджер или CI pipeline, я хочу регистрировать релиз-кандидат, импортировать артефакты и строить manifest без TUI, чтобы вход в release workflow был воспроизводимым в автоматизированном или headless сценарии. + +**Why this priority**: Это базовая ценность редизайна. Пока жизненный цикл кандидата зависит от TUI, release flow не годится для автоматизации и надёжного enterprise use. + +**Independent Test**: Можно создать кандидата, импортировать набор артефактов, построить manifest и запросить overview только через CLI или HTTP API, не используя интерактивный интерфейс. + +**Acceptance Scenarios**: + +1. **Given** у оператора есть описание релиз-кандидата и набор артефактов, **When** он выполняет headless workflow регистрации, импорта и построения manifest, **Then** система сохраняет кандидата и immutable snapshot manifest без участия TUI. +2. **Given** manifest успешно построен, **When** оператор запрашивает candidate overview через CLI или API, **Then** он получает актуальный lifecycle state и идентификатор latest manifest. +3. **Given** pipeline подготавливает следующий этап release workflow, **When** он использует headless candidate and manifest operations, **Then** ему не требуется интерактивный интерфейс для продолжения release process. + +--- + +### User Story 2 - Trusted and immutable compliance evidence (Priority: P1) + +Как сотрудник комплаенса или аудита, я хочу, чтобы policy, registry, manifest, run, violations и report были отделены друг от друга и сохранялись как доверенные или append-only сущности, чтобы итог выпуска был доказуемым и не зависел от UI-состояния или случайных env/json подмен. + +**Why this priority**: Главная архитектурная проблема текущего решения - смешение trust boundaries и UI logic с бизнес-решениями. Без исправления этого нельзя считать результат compliance надёжным. + +**Independent Test**: Можно зафиксировать policy snapshot и registry snapshot, выполнить run, затем проверить, что manifest/report/violations не переписывались и что итоговый статус выводится только из сохранённого evidence. + +**Acceptance Scenarios**: + +1. **Given** активная policy получена из доверенного policy store, **When** запускается compliance, **Then** run использует snapshot policy и snapshot registry, а не значения из UI или временных env-переменных. +2. **Given** для кандидата уже создан manifest, **When** входные данные кандидата меняются, **Then** система создаёт новый version manifest вместо изменения существующего snapshot. +3. **Given** run завершён, **When** оператор запрашивает историю проверки, **Then** старые run, violations и reports остаются доступными и не удаляются в real mode. + +--- + +### User Story 3 - Controlled approval and publication gate (Priority: P2) + +Как владелец релиза, я хочу выполнять approve, publish и revoke только после валидного compliance результата, чтобы состояние выпуска было формально управляемым и не зависело от ручных договорённостей. + +**Why this priority**: Проверка compliance имеет смысл только тогда, когда она реально управляет дальнейшими переходами release lifecycle. + +**Independent Test**: Можно попытаться утвердить или опубликовать кандидата в запрещённом состоянии и убедиться, что система блокирует переход; затем пройти валидный путь `CHECK_PASSED -> APPROVED -> PUBLISHED`. + +**Acceptance Scenarios**: + +1. **Given** кандидат имеет последний report со статусом `PASSED`, **When** уполномоченный оператор выполняет approve, **Then** система фиксирует approval decision и переводит кандидата в состояние `APPROVED`. +2. **Given** кандидат не имеет успешного compliance report или latest approval decision имеет значение `REJECTED`, **When** оператор пытается выполнить approve или publish вне допустимого правила, **Then** система отклоняет действие и сообщает причину блокировки. +3. **Given** кандидат опубликован в целевой канал, **When** требуется отзыв выпуска, **Then** система создаёт publication revocation record без удаления исходной истории публикации. + +--- + +### User Story 4 - Thin operational interfaces (Priority: P3) + +Как инженер сопровождения, я хочу использовать TUI и другие интерфейсы только как клиенты чтения и запуска операций, чтобы интерфейсы не содержали скрытой бизнес-логики, не подменяли policy и не создавали визуальные fake-runs. + +**Why this priority**: Тонкие интерфейсы упрощают сопровождение, делают поведение одинаковым между CLI, API, TUI и будущим Web UI, а также уменьшают риск расхождения логики. + +**Independent Test**: Можно открыть TUI, выполнить build manifest, run compliance, approve и publish, а затем проверить, что все операции и их последствия совпадают с CLI/API сценариями и не зависят от внутреннего UI state. + +**Acceptance Scenarios**: + +1. **Given** оператор открывает TUI, **When** он запускает build manifest или compliance, **Then** TUI вызывает application services и только отображает актуальные DTO/статусы. +2. **Given** система работает в demo mode, **When** оператор запускает demo workflow, **Then** demo данные и demo history остаются полностью изолированными от real mode. +3. **Given** оператор работает в headless окружении без TTY, **When** он пытается использовать функциональность release lifecycle, **Then** система направляет его в CLI/API путь, а не переводит TUI в скрытый псевдо-headless режим. + +### Edge Cases + +- Что происходит, если артефакт имеет расхождение между `declared_category` и `detected_category`? +- Что происходит, если кандидат изменился после построения manifest, но до запуска compliance? +- Как система различает business blocked, invalid input и system error при возврате статуса и exit code? +- Что происходит, если policy store недоступен в момент запроса compliance? +- Как обрабатывается повторный approve или повторный publish для уже терминально обработанного кандидата? +- Что происходит, если run был отменён или task crashed после старта, но до генерации report? +- Как система предотвращает пересечение demo namespace и real namespace в storage и audit history? + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: Система MUST предоставлять программно управляемый release lifecycle, доступный без TUI через CLI и HTTP API. +- **FR-002**: Система MUST поддерживать отдельную сущность release candidate с идентификатором, версией, ссылкой на source snapshot, provenance и статусом lifecycle. +- **FR-003**: Система MUST поддерживать импорт и хранение candidate artifacts с checksum, размером, declared category и detected category. +- **FR-004**: Система MUST различать declared category и detected category и фиксировать факт их расхождения как часть compliance evidence. +- **FR-005**: Система MUST строить distribution manifest как отдельный immutable snapshot с собственным идентификатором, version, digest и полным содержимым. +- **FR-006**: Если состав кандидата изменился после построения manifest, система MUST создавать новый manifest version вместо изменения существующего snapshot. +- **FR-007**: Система MUST получать policy только из доверенного policy store и сохранять используемую policy как immutable policy snapshot. +- **FR-008**: Система MUST получать source registry только из доверенного registry source и сохранять используемый registry как immutable snapshot. +- **FR-009**: Ни один интерфейс, пользовательский ввод или env-переменная MUST NOT определять содержимое policy snapshot, registry snapshot, итоговый report status или audit history. +- **FR-010**: Система MUST создавать отдельную сущность compliance run, связанную с candidate, manifest, policy snapshot и registry snapshot. +- **FR-011**: Compliance run MUST сохранять append-only stage execution records, violations и итоговый report. +- **FR-012**: Система MUST поддерживать статусы выполнения run не менее чем `PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`, `CANCELLED`. +- **FR-013**: Система MUST поддерживать итоговые статусы compliance не менее чем `PASSED`, `BLOCKED`, `ERROR`. +- **FR-014**: Система MUST различать outcome types `PASSED`, `BLOCKED`, `ERROR_INVALID_INPUT`, `ERROR_SYSTEM` и делать их однозначно наблюдаемыми через CLI/API. +- **FR-015**: Compliance pipeline MUST состоять из независимых стадий с отдельным stage result, decision, details и violations. +- **FR-016**: Система MUST позволять расширять compliance pipeline новыми стадиями без переписывания интерфейсов запуска и чтения run. +- **FR-017**: Release candidate lifecycle MUST поддерживать разрешённые переходы `DRAFT -> PREPARED -> MANIFEST_BUILT -> CHECK_PENDING -> CHECK_RUNNING -> CHECK_PASSED|CHECK_BLOCKED|CHECK_ERROR -> APPROVED -> PUBLISHED -> REVOKED`. +- **FR-018**: Система MUST блокировать `APPROVED` без успешного compliance result и MUST блокировать `PUBLISHED` без approval. +- **FR-019**: Система MUST сохранять отдельный approval decision с actor, временем, комментарием и ссылкой на report. +- **FR-020**: Система MUST трактовать approval и reject как immutable decisions, где latest decision governs publication gate; `REJECTED` блокирует публикацию, но не меняет compliance evidence и не переписывает lifecycle state кандидата. +- **FR-021**: Система MUST сохранять отдельный publication record с channel, actor, временем, ref и статусом, а revoke MUST оформляться как отдельное действие без удаления исходной публикации. +- **FR-022**: Каждое long-running compliance execution MUST выполняться как асинхронная task execution единица с наблюдаемым lifecycle, логами stage events и итоговой summary. +- **FR-023**: API запуск compliance MUST быть non-blocking и возвращать идентификаторы run и связанной task execution для последующего наблюдения. +- **FR-024**: Система MUST вести append-only audit log для создания кандидата, импорта артефактов, построения manifest, запуска compliance, завершения стадий, фиксации violations, генерации report, approval, publication и revoke. +- **FR-025**: В real mode система MUST NOT удалять historical runs, reports, violations или audit events. +- **FR-026**: Demo mode MUST использовать отдельный storage namespace, отдельные policy snapshots и отдельную историю, полностью изолированную от real mode. +- **FR-027**: TUI MUST быть thin client интерфейсом чтения и запуска операций и MUST NOT скрыто строить manifest, подмешивать policy, подменять registry, очищать history реального режима или создавать fake run только ради UI. +- **FR-028**: Для headless сценариев система MUST предоставлять CLI команды для candidate lifecycle, compliance, approval, publication и revoke. +- **FR-029**: Система MUST предоставлять HTTP endpoints для candidate lifecycle, manifest operations, compliance runs, report retrieval, approval и publication. +- **FR-030**: Система MUST предоставлять сводное представление candidate overview с данными о последнем manifest, последнем run, последнем report, latest policy snapshot, latest approval/publication state и текущем lifecycle state. +- **FR-031**: Compliance report MUST быть отдельной immutable сущностью с финальным статусом, summary и связью с конкретным run. +- **FR-032**: Система MUST позволять оператору получать список violations и stage details отдельно от итогового report. +- **FR-033**: Business actions для candidate, manifest, compliance, approval и publication MUST использовать единый application facade, одинаковый для CLI, TUI, API и будущего Web UI. + +### Key Entities *(include if feature involves data)* + +- **Release Candidate**: Объект управления выпуском, описывающий, что именно собираются выпустить и в каком lifecycle состоянии это находится. +- **Candidate Artifact**: Артефакт релиз-кандидата с доказуемыми признаками происхождения, контрольной суммой и двумя категориями классификации. +- **Distribution Manifest**: Immutable snapshot состава поставки, зафиксированный для конкретного кандидата и конкретной версии manifest. +- **Clean Policy Snapshot**: Доверенный immutable snapshot policy, использованный во время проверки. +- **Source Registry Snapshot**: Доверенный immutable snapshot реестра разрешённых источников. +- **Compliance Run**: Операционная сущность, представляющая отдельный запуск проверки для кандидата и manifest. +- **Compliance Stage Run**: Результат выполнения отдельной стадии pipeline внутри конкретного run. +- **Compliance Violation**: Зафиксированное нарушение с severity, evidence и ссылкой на stage/run. +- **Compliance Report**: Отдельный immutable итоговый отчёт по завершённому run. +- **Approval Decision**: Формальный акт approve/reject с actor, comment и ссылкой на report. +- **Publication Record**: Формальная запись публикации или отзыва с channel и статусом. +- **Candidate Overview**: Производное представление для интерфейсов, объединяющее состояние кандидата, latest manifest, latest run и latest report. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: 100% обязательных действий release lifecycle для стандартного сценария (`register -> import-artifacts -> build-manifest -> run-compliance -> approve -> publish`) выполняются без TUI через CLI или API. +- **SC-002**: 100% завершённых compliance runs оставляют неизменяемый report и связанный audit trail, доступный для чтения после завершения. +- **SC-003**: 100% попыток approve без успешного compliance result и publish без approval отклоняются системой. +- **SC-004**: Не менее 95% стандартных запусков compliance получают machine-readable outcome без ручного исправления состояния системы. +- **SC-005**: Не менее 90% операторов могут пройти базовый release workflow по quickstart без обращения к скрытым TUI-сценариям или ручным DB-операциям. +- **SC-006**: Время получения идентификатора run/task после запроса запуска compliance не превышает 2 секунд для типового кандидата. +- **SC-007**: В 100% real-mode сценариев historical runs, violations, reports и audit events не удаляются командами пользовательского интерфейса. + +## Assumptions + +- В проекте уже доступен общий TaskManager, пригодный для оркестрации long-running compliance tasks. +- Для policy и registry может быть предоставлен доверенный read-only источник, отдельный от пользовательского UI input. +- Существующий clean-release модуль допускает поэтапную миграцию без одномоментного удаления старых API/TUI entrypoints. +- Операторы релиза, approval actors и publication actors аутентифицированы существующими механизмами приложения. +- Demo mode нужен для демонстрации и тестовых сценариев, но не должен влиять на real mode evidence. diff --git a/specs/025-clean-release-compliance/tasks.md b/specs/025-clean-release-compliance/tasks.md new file mode 100644 index 00000000..7029da2f --- /dev/null +++ b/specs/025-clean-release-compliance/tasks.md @@ -0,0 +1,225 @@ +# Tasks: Clean Release Compliance Subsystem Redesign + +**Input**: Design documents from [`/specs/025-clean-release-compliance/`](specs/025-clean-release-compliance) +**Prerequisites**: [`plan.md`](specs/025-clean-release-compliance/plan.md), [`spec.md`](specs/025-clean-release-compliance/spec.md), [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md), [`research.md`](specs/025-clean-release-compliance/research.md), [`data-model.md`](specs/025-clean-release-compliance/data-model.md), [`contracts/`](specs/025-clean-release-compliance/contracts) + +**Tests**: Include service, API, CLI and TUI smoke tests because this is a lifecycle-critical subsystem redesign. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing. + +## Format: `[ID] [P?] [Story] Description` + +--- + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Prepare new clean-release redesign scaffolding, fixtures and test entrypoints. + +- [ ] T001 Create clean release redesign module skeletons in `backend/src/services/clean_release/` and `backend/src/services/clean_release/repositories/` +- [ ] T002 [P] Add redesign fixture set in `backend/tests/fixtures/clean_release/fixtures_release_v2.json` +- [ ] T003 [P] Add API contract test scaffolding in `backend/src/api/routes/__tests__/test_clean_release_v2_api.py` and `backend/src/api/routes/__tests__/test_clean_release_v2_release_api.py` +- [ ] T004 [P] Add CLI and TUI smoke test scaffolding in `backend/tests/scripts/test_clean_release_cli.py` and `backend/tests/scripts/test_clean_release_tui_v2.py` + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Build canonical lifecycle, persistence boundaries and shared facade before any user story. + +- [ ] T005 Implement clean release enums, exceptions and DTOs in `backend/src/services/clean_release/enums.py`, `backend/src/services/clean_release/exceptions.py` and `backend/src/services/clean_release/dto.py` +- [ ] T006 Implement canonical clean release domain entities and lifecycle guards in `backend/src/models/clean_release.py` (CRITICAL: PRE valid aggregate identifiers and state commands; POST immutable evidence and valid transitions only; TESTS: invalid transition, manifest immutability, publish gate) +- [ ] T007 [P] Implement repository interfaces and durable adapters in `backend/src/services/clean_release/repositories/candidate_repository.py`, `backend/src/services/clean_release/repositories/artifact_repository.py`, `backend/src/services/clean_release/repositories/manifest_repository.py`, `backend/src/services/clean_release/repositories/policy_repository.py`, `backend/src/services/clean_release/repositories/compliance_repository.py`, `backend/src/services/clean_release/repositories/report_repository.py`, `backend/src/services/clean_release/repositories/approval_repository.py`, `backend/src/services/clean_release/repositories/publication_repository.py` and `backend/src/services/clean_release/repositories/audit_repository.py` +- [ ] T008 [P] Implement facade and DTO mapping in `backend/src/services/clean_release/facade.py` and `backend/src/services/clean_release/mappers.py` +- [ ] T009 Wire clean release dependencies for repositories, trusted policy access and task manager in `backend/src/dependencies.py` +- [ ] T009a Implement `ConfigManager`-backed resolution for trusted policy store, profile selection, mode and storage wiring in `backend/src/dependencies.py` and `backend/src/services/clean_release/policy_resolution_service.py` +- [ ] T010 Add legacy compatibility shim and migration helpers in `backend/src/services/clean_release/__init__.py` and `backend/src/services/clean_release/repository.py` + +**Checkpoint**: Foundational layer complete; user stories can proceed. + +--- + +## Phase 3: User Story 1 - Headless release candidate lifecycle (Priority: P1) 🎯 MVP + +**Goal**: Make candidate registration, artifact import, manifest build and lifecycle visibility available through CLI/API without TUI. + +**Independent Test**: Register candidate, import artifacts, build manifest and query overview using only CLI/API. + +### Tests for User Story 1 + +- [ ] T011 [P] [US1] Add lifecycle and manifest versioning tests in `backend/tests/services/clean_release/test_candidate_manifest_services.py` +- [ ] T012 [P] [US1] Add API contract tests for candidate/artifact/manifest endpoints in `backend/src/api/routes/__tests__/test_clean_release_v2_api.py` +- [ ] T013 [P] [US1] Add CLI smoke tests for candidate register/import/manifest build in `backend/tests/scripts/test_clean_release_cli.py` + +### Implementation for User Story 1 + +- [ ] T014 [US1] Implement candidate preparation service in `backend/src/services/clean_release/candidate_service.py` (CRITICAL: PRE unique candidate id and valid artifacts; POST candidate/artifacts persisted and status advances only through legal states; TESTS: duplicate id, malformed artifact input, empty artifact set) +- [ ] T015 [US1] Implement manifest service in `backend/src/services/clean_release/manifest_service.py` (CRITICAL: PRE candidate prepared and artifacts available; POST immutable manifest snapshot with deterministic digest and version increment; TESTS: rebuild creates new version, existing manifest cannot be mutated, missing candidate rejected) +- [ ] T016 [US1] Implement policy resolution service with trusted snapshot reads in `backend/src/services/clean_release/policy_resolution_service.py` (CRITICAL: PRE trusted profile exists; POST immutable policy and registry snapshots without UI/env overrides; TESTS: missing profile, missing registry, override attempt) +- [ ] T017 [US1] Implement candidate and manifest CLI commands in `backend/src/scripts/clean_release_cli.py` +- [ ] T018 [US1] Implement candidate/artifact/manifest REST endpoints and expanded overview DTO mapping in `backend/src/api/routes/clean_release.py` +- [ ] T019 [US1] Verify implementation matches [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md) (Happy Path & Errors) + +**Checkpoint**: US1 independently functional and usable from headless automation. + +--- + +## Phase 4: User Story 2 - Trusted and immutable compliance evidence (Priority: P1) + +**Goal**: Execute compliance as an observable, append-only TaskManager-backed pipeline with immutable reports and trusted snapshots. + +**Independent Test**: Start a run through API/CLI, observe task/run progress, inspect stage records and violations, then verify immutable final report persistence. + +### Tests for User Story 2 + +- [ ] T020 [P] [US2] Add stage pipeline and run finalization tests in `backend/tests/services/clean_release/test_compliance_execution_service.py` +- [ ] T021 [P] [US2] Add TaskManager integration tests for clean release runs in `backend/tests/services/clean_release/test_compliance_task_integration.py` +- [ ] T022 [P] [US2] Add report and audit immutability tests in `backend/tests/services/clean_release/test_report_audit_immutability.py` + +### Implementation for User Story 2 + +- [ ] T023 [US2] Implement pluggable stage base and default stage modules in `backend/src/services/clean_release/stages/base.py`, `backend/src/services/clean_release/stages/data_purity.py`, `backend/src/services/clean_release/stages/internal_sources_only.py`, `backend/src/services/clean_release/stages/no_external_endpoints.py` and `backend/src/services/clean_release/stages/manifest_consistency.py` +- [ ] T024 [US2] Implement compliance execution service in `backend/src/services/clean_release/compliance_execution_service.py` (CRITICAL: PRE candidate exists and explicit or latest manifest plus trusted snapshots are resolvable; POST run, stage records, violations and report remain mutually consistent; TESTS: run without manifest, task crash mid-run, blocked report finalization) +- [ ] T025 [US2] Bind compliance runs to TaskManager and reports service in `backend/src/services/clean_release/compliance_execution_service.py`, `backend/src/services/reports/report_service.py` and `backend/src/dependencies.py` +- [ ] T026 [US2] Implement compliance REST endpoints for run creation, run status, stages, violations and report in `backend/src/api/routes/clean_release.py` +- [ ] T027 [US2] Implement compliance CLI commands (`run`, `status`, `report`, `violations`) in `backend/src/scripts/clean_release_cli.py` with latest-manifest fallback when `--manifest-id` is omitted +- [ ] T028 [US2] Implement append-only audit hooks for run lifecycle and violations in `backend/src/services/clean_release/audit_service.py` +- [ ] T029 [US2] Verify implementation matches [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md) (Happy Path & Errors) + +**Checkpoint**: US2 independently functional with real run evidence and immutable reporting. + +--- + +## Phase 5: User Story 3 - Controlled approval and publication gate (Priority: P2) + +**Goal**: Enforce legal approval/publication transitions over completed compliance results. + +**Independent Test**: Attempt invalid approve/publish transitions, then complete the valid `CHECK_PASSED -> APPROVED -> PUBLISHED -> REVOKED` flow. + +### Tests for User Story 3 + +- [ ] T030 [P] [US3] Add approval gate tests in `backend/tests/services/clean_release/test_approval_service.py` +- [ ] T031 [P] [US3] Add publication gate tests in `backend/tests/services/clean_release/test_publication_service.py` +- [ ] T032 [P] [US3] Add API/CLI tests for approve, reject, publish and revoke in `backend/src/api/routes/__tests__/test_clean_release_v2_release_api.py` and `backend/tests/scripts/test_clean_release_cli.py` + +### Implementation for User Story 3 + +- [ ] T033 [US3] Implement approval service in `backend/src/services/clean_release/approval_service.py` (CRITICAL: PRE report belongs to candidate and final status is PASSED for approve; POST immutable decision persisted, approve may advance candidate state, reject blocks publication gate without rewriting compliance evidence; TESTS: approve blocked report, approve foreign report, duplicate approve, reject then publish) +- [ ] T034 [US3] Implement publication service in `backend/src/services/clean_release/publication_service.py` (CRITICAL: PRE candidate approved; POST immutable publication/revocation record and legal state transition; TESTS: publish without approval, revoke unknown publication, republish after revoke) +- [ ] T035 [US3] Implement release CLI commands (`approve`, `reject`, `publish`, `revoke`) in `backend/src/scripts/clean_release_cli.py` +- [ ] T036 [US3] Implement release REST endpoints in `backend/src/api/routes/clean_release.py` +- [ ] T037 [US3] Extend facade overview/read models for policy snapshot, approval and publication state in `backend/src/services/clean_release/facade.py` and `backend/src/services/clean_release/dto.py` +- [ ] T038 [US3] Verify implementation matches [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md) (Happy Path & Errors) + +**Checkpoint**: US3 independently functional with explicit release gates. + +--- + +## Phase 6: User Story 4 - Thin operational interfaces (Priority: P3) + +**Goal**: Convert TUI into a real thin client and isolate demo behavior from real-mode evidence. + +**Independent Test**: Operate the same candidate through TUI using facade-backed actions and confirm that TUI behavior matches CLI/API semantics without hidden side effects. + +### Tests for User Story 4 + +- [ ] T039 [P] [US4] Add TUI thin-client smoke tests for facade actions and blocked transitions in `backend/tests/scripts/test_clean_release_tui_v2.py` +- [ ] T040 [P] [US4] Add demo namespace isolation tests in `backend/tests/services/clean_release/test_demo_mode_isolation.py` +- [ ] T041 [P] [US4] Add non-TTY startup behavior tests in `backend/tests/scripts/test_clean_release_tui_v2.py` + +### Implementation for User Story 4 + +- [ ] T042 [US4] Refactor TUI to call only facade methods and render DTOs in `backend/src/scripts/clean_release_tui.py` (CRITICAL: PRE valid TTY and candidate context; POST no hidden manifest/policy/run mutations outside facade; TESTS: no TTY, missing manifest on F5, blocked report on F8) +- [ ] T043 [US4] Implement isolated demo data service and namespace handling in `backend/src/services/clean_release/demo_data_service.py` and `backend/src/services/clean_release/repositories/` +- [ ] T044 [US4] Remove real-mode `clear_history` and pseudo-headless fallback behavior in `backend/src/scripts/clean_release_tui.py` +- [ ] T045 [US4] Implement TUI overview panels and action keys `F5/F6/F7/F8/F9/F10` aligned with facade DTOs in `backend/src/scripts/clean_release_tui.py` +- [ ] T046 [US4] Verify implementation matches [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md) (Happy Path & Errors) + +**Checkpoint**: US4 independently functional with thin-client TUI and isolated demo mode. + +--- + +## Phase 7: Polish & Cross-Cutting Concerns + +**Purpose**: Finalize migration, compatibility and operational documentation. + +- [ ] T047 [P] Add compatibility/deprecation tests for legacy `/api/clean-release/checks*` and `/api/clean-release/candidates/prepare` paths in `backend/src/api/routes/__tests__/test_clean_release_legacy_compat.py` +- [ ] T048 [P] Update operational documentation for new CLI/API/TUI workflow in `README.md` and `docs/installation.md` +- [ ] T049 Run end-to-end quickstart validation and capture results in `specs/025-clean-release-compliance/quickstart.md` +- [ ] T050 Migrate or wrap legacy clean release modules in `backend/src/services/clean_release/preparation_service.py`, `backend/src/services/clean_release/manifest_builder.py`, `backend/src/services/clean_release/compliance_orchestrator.py` and `backend/src/services/clean_release/repository.py` +- [ ] T051 Align clean release report surfacing with shared reports/task views in `backend/src/services/reports/report_service.py` and `backend/src/api/routes/reports.py` +- [ ] T052 Run semantic compliance review for touched clean release modules and close critical `[DEF]`/contract gaps in `backend/src/models/clean_release.py`, `backend/src/services/clean_release/` and `backend/src/scripts/clean_release_tui.py` + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1 (Setup)**: No dependencies. +- **Phase 2 (Foundational)**: Depends on Phase 1 and blocks all stories. +- **Phase 3 (US1)**: Depends on Phase 2. +- **Phase 4 (US2)**: Depends on Phase 2 and reuses outputs from US1 trusted snapshot and facade work. +- **Phase 5 (US3)**: Depends on Phase 2 and a stable report model from US2. +- **Phase 6 (US4)**: Depends on Phases 3-5 because TUI must sit on stable facade/API semantics. +- **Phase 7 (Polish)**: Depends on all selected stories. + +### User Story Dependencies + +- **US1 (P1)**: First deliverable and MVP. +- **US2 (P1)**: Depends on facade/repository foundations and benefits from US1 candidate/manifest flow. +- **US3 (P2)**: Depends on successful report persistence from US2. +- **US4 (P3)**: Depends on stable facade and release-gate behavior from US1-US3. + +Graph: `US1 -> US2 -> US3 -> US4` + +### Parallel Opportunities + +- Setup tasks T002, T003, T004. +- Foundational tasks T007 and T008 after T005/T006 are stable. +- US1 tests T011, T012, T013. +- US2 tests T020, T021, T022. +- US3 tests T030, T031, T032. +- US4 tests T039, T040, T041. +- Polish tasks T047 and T048. + +--- + +## Parallel Example: User Story 1 + +```bash +Task: "T011 [US1] Add lifecycle and manifest tests in backend/tests/services/clean_release/test_candidate_manifest_services.py" +Task: "T012 [US1] Add API contract tests in backend/src/api/routes/__tests__/test_clean_release_v2_api.py" +Task: "T013 [US1] Add CLI smoke tests in backend/tests/scripts/test_clean_release_cli.py" +``` + +## Parallel Example: User Story 2 + +```bash +Task: "T020 [US2] Add stage pipeline tests in backend/tests/services/clean_release/test_compliance_execution_service.py" +Task: "T021 [US2] Add TaskManager integration tests in backend/tests/services/clean_release/test_compliance_task_integration.py" +Task: "T022 [US2] Add report immutability tests in backend/tests/services/clean_release/test_report_audit_immutability.py" +``` + +--- + +## Implementation Strategy + +### MVP First (Recommended) + +1. Complete Phase 1 and Phase 2. +2. Deliver Phase 3 (US1) so candidate lifecycle works headlessly through CLI/API. +3. Validate independent test for US1. +4. Then add US2 for trusted compliance evidence before moving to release gates and TUI refactor. + +### Incremental Delivery + +1. US1: headless candidate lifecycle. +2. US2: trusted compliance execution + immutable evidence. +3. US3: approval/publication gate. +4. US4: thin TUI + demo isolation. +5. Phase 7: compatibility, docs and semantic cleanup. + +### UX Preservation Rule + +No task in this plan is allowed to reintroduce hidden business logic into TUI or to degrade the explicit operator flow in [`ux_reference.md`](specs/025-clean-release-compliance/ux_reference.md). +Each user story contains a mandatory UX verification task: T019, T029, T038, T046. diff --git a/specs/025-clean-release-compliance/ux_reference.md b/specs/025-clean-release-compliance/ux_reference.md new file mode 100644 index 00000000..988dbef5 --- /dev/null +++ b/specs/025-clean-release-compliance/ux_reference.md @@ -0,0 +1,95 @@ +# UX Reference: Clean Release Compliance Subsystem Redesign + +**Feature Branch**: `025-clean-release-compliance` +**Created**: 2026-03-09 +**Status**: Draft + +## 1. User Persona & Context + +- **Who is the user?**: Release manager, compliance operator, enterprise support engineer. +- **What is their goal?**: Safely move a release candidate through candidate preparation, compliance, approval and publication without hidden state. +- **Context**: Usually works in terminal-first infrastructure, often in restricted or headless environments, sometimes with a TTY, sometimes from CI. + +## 2. The Happy Path Narrative + +Оператор регистрирует кандидата и импортирует артефакты через CLI или API. Затем он открывает TUI и сразу видит candidate overview: latest manifest, active policy snapshot, latest run, violations и publication state. Нажатие `F6` строит manifest, `F5` запускает compliance, а экран показывает реальный прогресс stage-by-stage из task/run logs, а не локальную имитацию. После `PASSED` оператор выполняет `F8` approve и `F9` publish, и каждое действие мгновенно отражается в overview без скрытых сайд-эффектов. + +## 3. Interface Mockups + +### CLI Interaction + +```bash +$ clean-release candidate register \ + --candidate-id 2026.03.09-rc1 \ + --version 1.2.0 \ + --source-snapshot v1.2.0-rc1 + +Candidate created: 2026.03.09-rc1 +Status: DRAFT + +$ clean-release manifest build --candidate-id 2026.03.09-rc1 +Manifest created: man-001 +Manifest digest: sha256:9fa... +Status: MANIFEST_BUILT + +$ clean-release compliance run --candidate-id 2026.03.09-rc1 --json +{ + "run_id": "run-001", + "candidate_id": "2026.03.09-rc1", + "status": "PENDING", + "task_id": "task-123" +} +``` + +### TUI Layout & Flow + +**Screen/Component**: Clean Release Overview + +- **Layout**: Three-pane terminal layout. + - Top header: current candidate, lifecycle state, active mode (`real` or `demo`). + - Left pane: candidate summary, latest manifest, approval/publication state. + - Right pane: latest compliance run, stage timeline, violations list. + - Bottom action bar: `F5 Run`, `F6 Manifest`, `F7 Refresh`, `F8 Approve`, `F9 Publish`, `F10 Exit`. +- **Key Elements**: + - **Candidate Summary**: shows candidate id, version, source snapshot, current state. + - **Latest Manifest Card**: manifest id, version, digest, created time. + - **Policy Snapshot Card**: policy id/version and registry version used for latest run. + - **Violations Table**: severity, code, artifact path, short message. +- **States**: + - **Default**: Existing overview visible, no hidden mutation. + - **Running**: Current run and current stage are highlighted; logs update live from task events. + - **Passed**: Action bar enables `Approve` when transition is legal. + - **Blocked/Error**: Violations or failure reason become the dominant focus; approval/publish actions stay disabled. + +## 4. The Error Experience + +**Philosophy**: Surface the real state and tell the operator what transition is blocked. Never hide missing prerequisites by auto-fixing them in the UI. + +### Scenario A: Missing Manifest + +- **User Action**: Presses `F5` to run compliance before a manifest exists. +- **System Response**: + - TUI: inline error banner `Manifest required before compliance run` and highlight on `F6 Build manifest`. + - CLI: `Error: candidate 2026.03.09-rc1 has no manifest. Build a manifest first.` +- **Recovery**: Operator runs build manifest, returns to overview, retries compliance. + +### Scenario B: Blocked By Policy + +- **System Response**: Run ends in `BLOCKED`, latest report card turns warning state, violations table is focused automatically. +- **Recovery**: Operator can inspect violations, export/report details, fix candidate inputs, build a new manifest and request a new run. + +### Scenario C: Policy Store Unavailable + +- **System Response**: Request is rejected as input/system error before stage execution; UI explicitly says policy snapshot could not be resolved. +- **Recovery**: Retry when trusted policy source is restored. No fake run is shown. + +### Scenario D: Headless Environment + +- **System Response**: TUI refuses to start without TTY and instructs operator to use CLI/API flow. +- **Recovery**: Run equivalent `clean-release ...` command or call API. + +## 5. Tone & Voice + +- **Style**: Concise, operational, deterministic. +- **Terminology**: Use `candidate`, `manifest`, `policy snapshot`, `compliance run`, `report`, `approval`, `publication` consistently. +- **Avoided Terms**: Avoid vague legacy words like `checker`, `fake run`, `history cleanup`, `headless ready` inside the operator UX.